@yourgpt/copilot-sdk 0.1.0
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/README.md +61 -0
- package/dist/chunk-2ZCWVAAK.cjs +1915 -0
- package/dist/chunk-2ZCWVAAK.cjs.map +1 -0
- package/dist/chunk-N4OA2J32.js +1844 -0
- package/dist/chunk-N4OA2J32.js.map +1 -0
- package/dist/chunk-QUGTRQSS.js +3100 -0
- package/dist/chunk-QUGTRQSS.js.map +1 -0
- package/dist/chunk-W6KQT7YZ.cjs +3152 -0
- package/dist/chunk-W6KQT7YZ.cjs.map +1 -0
- package/dist/core/index.cjs +288 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +870 -0
- package/dist/core/index.d.ts +870 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/react/index.cjs +137 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +1824 -0
- package/dist/react/index.d.ts +1824 -0
- package/dist/react/index.js +4 -0
- package/dist/react/index.js.map +1 -0
- package/dist/styles.css +48 -0
- package/dist/thread-C2FjuGLb.d.cts +1225 -0
- package/dist/thread-C2FjuGLb.d.ts +1225 -0
- package/dist/ui/index.cjs +4116 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +1143 -0
- package/dist/ui/index.d.ts +1143 -0
- package/dist/ui/index.js +4019 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,3100 @@
|
|
|
1
|
+
import { isConsoleCaptureActive, startConsoleCapture, isNetworkCaptureActive, startNetworkCapture, stopConsoleCapture, stopNetworkCapture, isScreenshotSupported, captureScreenshot, getConsoleLogs, getNetworkRequests, clearConsoleLogs, clearNetworkRequests, formatLogsForAI, formatRequestsForAI, detectIntent, streamSSE, zodObjectToInputSchema } from './chunk-N4OA2J32.js';
|
|
2
|
+
import { createContext, useContext, useCallback, useEffect, useState, useRef, useSyncExternalStore, useMemo } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import * as z from 'zod';
|
|
5
|
+
|
|
6
|
+
// src/chat/types/tool.ts
|
|
7
|
+
var initialAgentLoopState = {
|
|
8
|
+
toolExecutions: [],
|
|
9
|
+
iteration: 0,
|
|
10
|
+
maxIterations: 20,
|
|
11
|
+
maxIterationsReached: false,
|
|
12
|
+
isProcessing: false
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/chat/interfaces/ChatState.ts
|
|
16
|
+
var SimpleChatState = class {
|
|
17
|
+
constructor() {
|
|
18
|
+
this._messages = [];
|
|
19
|
+
this._status = "ready";
|
|
20
|
+
this._error = void 0;
|
|
21
|
+
this.callbacks = /* @__PURE__ */ new Set();
|
|
22
|
+
}
|
|
23
|
+
get messages() {
|
|
24
|
+
return this._messages;
|
|
25
|
+
}
|
|
26
|
+
set messages(value) {
|
|
27
|
+
this._messages = value;
|
|
28
|
+
this.notify();
|
|
29
|
+
}
|
|
30
|
+
get status() {
|
|
31
|
+
return this._status;
|
|
32
|
+
}
|
|
33
|
+
set status(value) {
|
|
34
|
+
this._status = value;
|
|
35
|
+
this.notify();
|
|
36
|
+
}
|
|
37
|
+
get error() {
|
|
38
|
+
return this._error;
|
|
39
|
+
}
|
|
40
|
+
set error(value) {
|
|
41
|
+
this._error = value;
|
|
42
|
+
this.notify();
|
|
43
|
+
}
|
|
44
|
+
pushMessage(message) {
|
|
45
|
+
this._messages = [...this._messages, message];
|
|
46
|
+
this.notify();
|
|
47
|
+
}
|
|
48
|
+
popMessage() {
|
|
49
|
+
this._messages = this._messages.slice(0, -1);
|
|
50
|
+
this.notify();
|
|
51
|
+
}
|
|
52
|
+
replaceMessage(index, message) {
|
|
53
|
+
this._messages = [
|
|
54
|
+
...this._messages.slice(0, index),
|
|
55
|
+
message,
|
|
56
|
+
...this._messages.slice(index + 1)
|
|
57
|
+
];
|
|
58
|
+
this.notify();
|
|
59
|
+
}
|
|
60
|
+
updateLastMessage(updater) {
|
|
61
|
+
if (this._messages.length === 0) return;
|
|
62
|
+
const lastIndex = this._messages.length - 1;
|
|
63
|
+
this.replaceMessage(lastIndex, updater(this._messages[lastIndex]));
|
|
64
|
+
}
|
|
65
|
+
setMessages(messages) {
|
|
66
|
+
this._messages = messages;
|
|
67
|
+
this.notify();
|
|
68
|
+
}
|
|
69
|
+
clearMessages() {
|
|
70
|
+
this._messages = [];
|
|
71
|
+
this.notify();
|
|
72
|
+
}
|
|
73
|
+
subscribe(callback) {
|
|
74
|
+
this.callbacks.add(callback);
|
|
75
|
+
return () => this.callbacks.delete(callback);
|
|
76
|
+
}
|
|
77
|
+
getMessagesSnapshot() {
|
|
78
|
+
return this._messages;
|
|
79
|
+
}
|
|
80
|
+
getStatusSnapshot() {
|
|
81
|
+
return this._status;
|
|
82
|
+
}
|
|
83
|
+
getErrorSnapshot() {
|
|
84
|
+
return this._error;
|
|
85
|
+
}
|
|
86
|
+
notify() {
|
|
87
|
+
this.callbacks.forEach((cb) => cb());
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/chat/functions/stream/parseSSE.ts
|
|
92
|
+
function parseSSELine(line) {
|
|
93
|
+
if (!line || line.trim() === "") {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (line.startsWith(":")) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (line.startsWith("data: ")) {
|
|
100
|
+
const data = line.slice(6);
|
|
101
|
+
if (data === "[DONE]") {
|
|
102
|
+
return { type: "done" };
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(data);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function isStreamDone(chunk) {
|
|
113
|
+
return chunk.type === "done" || chunk.type === "error";
|
|
114
|
+
}
|
|
115
|
+
function requiresToolExecution(chunk) {
|
|
116
|
+
if (chunk.type === "done" && chunk.requiresAction) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (chunk.type === "tool_calls") {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/chat/functions/stream/processChunk.ts
|
|
126
|
+
function processStreamChunk(chunk, state) {
|
|
127
|
+
switch (chunk.type) {
|
|
128
|
+
case "message:start":
|
|
129
|
+
return {
|
|
130
|
+
...state,
|
|
131
|
+
messageId: chunk.id
|
|
132
|
+
};
|
|
133
|
+
case "message:delta":
|
|
134
|
+
return {
|
|
135
|
+
...state,
|
|
136
|
+
content: state.content + chunk.content
|
|
137
|
+
};
|
|
138
|
+
case "message:end":
|
|
139
|
+
return {
|
|
140
|
+
...state,
|
|
141
|
+
finishReason: "stop"
|
|
142
|
+
};
|
|
143
|
+
case "thinking:delta":
|
|
144
|
+
return {
|
|
145
|
+
...state,
|
|
146
|
+
thinking: state.thinking + chunk.content
|
|
147
|
+
};
|
|
148
|
+
case "tool_calls":
|
|
149
|
+
return {
|
|
150
|
+
...state,
|
|
151
|
+
toolCalls: parseToolCalls(chunk.toolCalls),
|
|
152
|
+
requiresAction: true
|
|
153
|
+
};
|
|
154
|
+
case "done":
|
|
155
|
+
return {
|
|
156
|
+
...state,
|
|
157
|
+
requiresAction: chunk.requiresAction ?? false,
|
|
158
|
+
finishReason: "stop"
|
|
159
|
+
};
|
|
160
|
+
case "error":
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
finishReason: "error"
|
|
164
|
+
};
|
|
165
|
+
default:
|
|
166
|
+
return state;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function parseToolCalls(rawToolCalls) {
|
|
170
|
+
return rawToolCalls.map((tc) => {
|
|
171
|
+
const toolCall = tc;
|
|
172
|
+
if (toolCall.function) {
|
|
173
|
+
return {
|
|
174
|
+
id: toolCall.id,
|
|
175
|
+
name: toolCall.function.name,
|
|
176
|
+
args: JSON.parse(toolCall.function.arguments)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
id: toolCall.id,
|
|
181
|
+
name: toolCall.name ?? "",
|
|
182
|
+
args: toolCall.args ?? {}
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function createStreamState(messageId) {
|
|
187
|
+
return {
|
|
188
|
+
messageId,
|
|
189
|
+
content: "",
|
|
190
|
+
thinking: "",
|
|
191
|
+
toolCalls: [],
|
|
192
|
+
requiresAction: false,
|
|
193
|
+
finishReason: void 0
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/chat/functions/message/createMessage.ts
|
|
198
|
+
function generateMessageId() {
|
|
199
|
+
return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
200
|
+
}
|
|
201
|
+
function createUserMessage(content, attachments) {
|
|
202
|
+
return {
|
|
203
|
+
id: generateMessageId(),
|
|
204
|
+
role: "user",
|
|
205
|
+
content,
|
|
206
|
+
attachments,
|
|
207
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function streamStateToMessage(state) {
|
|
211
|
+
const toolCalls = state.toolCalls.length > 0 ? state.toolCalls.map((tc) => ({
|
|
212
|
+
id: tc.id,
|
|
213
|
+
type: "function",
|
|
214
|
+
function: {
|
|
215
|
+
name: tc.name,
|
|
216
|
+
arguments: JSON.stringify(tc.args)
|
|
217
|
+
}
|
|
218
|
+
})) : void 0;
|
|
219
|
+
return {
|
|
220
|
+
id: state.messageId,
|
|
221
|
+
role: "assistant",
|
|
222
|
+
content: state.content,
|
|
223
|
+
thinking: state.thinking || void 0,
|
|
224
|
+
toolCalls,
|
|
225
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function createEmptyAssistantMessage(id) {
|
|
229
|
+
return {
|
|
230
|
+
id: generateMessageId(),
|
|
231
|
+
role: "assistant",
|
|
232
|
+
content: "",
|
|
233
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/chat/adapters/HttpTransport.ts
|
|
238
|
+
var HttpTransport = class {
|
|
239
|
+
constructor(config) {
|
|
240
|
+
this.abortController = null;
|
|
241
|
+
this.streaming = false;
|
|
242
|
+
this.config = {
|
|
243
|
+
streaming: true,
|
|
244
|
+
timeout: 6e4,
|
|
245
|
+
...config
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Send a chat request
|
|
250
|
+
*/
|
|
251
|
+
async send(request) {
|
|
252
|
+
this.abortController = new AbortController();
|
|
253
|
+
this.streaming = true;
|
|
254
|
+
try {
|
|
255
|
+
const response = await fetch(this.config.url, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: {
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
...this.config.headers
|
|
260
|
+
},
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
messages: request.messages,
|
|
263
|
+
threadId: request.threadId,
|
|
264
|
+
systemPrompt: request.systemPrompt,
|
|
265
|
+
llm: request.llm,
|
|
266
|
+
tools: request.tools,
|
|
267
|
+
actions: request.actions,
|
|
268
|
+
streaming: this.config.streaming,
|
|
269
|
+
...request.body
|
|
270
|
+
}),
|
|
271
|
+
signal: this.abortController.signal
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const error = await response.text();
|
|
275
|
+
throw new Error(`HTTP ${response.status}: ${error}`);
|
|
276
|
+
}
|
|
277
|
+
const contentType = response.headers.get("content-type") || "";
|
|
278
|
+
if (contentType.includes("application/json")) {
|
|
279
|
+
this.streaming = false;
|
|
280
|
+
const json = await response.json();
|
|
281
|
+
return json;
|
|
282
|
+
}
|
|
283
|
+
if (!response.body) {
|
|
284
|
+
throw new Error("No response body");
|
|
285
|
+
}
|
|
286
|
+
return this.createStreamIterable(response.body);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
this.streaming = false;
|
|
289
|
+
if (error.name === "AbortError") {
|
|
290
|
+
return (async function* () {
|
|
291
|
+
})();
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Abort the current request
|
|
298
|
+
*/
|
|
299
|
+
abort() {
|
|
300
|
+
this.abortController?.abort();
|
|
301
|
+
this.abortController = null;
|
|
302
|
+
this.streaming = false;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Check if currently streaming
|
|
306
|
+
*/
|
|
307
|
+
isStreaming() {
|
|
308
|
+
return this.streaming;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Create an async iterable from a ReadableStream
|
|
312
|
+
*/
|
|
313
|
+
createStreamIterable(body) {
|
|
314
|
+
const reader = body.getReader();
|
|
315
|
+
const decoder = new TextDecoder();
|
|
316
|
+
let buffer = "";
|
|
317
|
+
const chunkQueue = [];
|
|
318
|
+
let streamDone = false;
|
|
319
|
+
const self = this;
|
|
320
|
+
return {
|
|
321
|
+
[Symbol.asyncIterator]() {
|
|
322
|
+
return {
|
|
323
|
+
async next() {
|
|
324
|
+
if (chunkQueue.length > 0) {
|
|
325
|
+
return { value: chunkQueue.shift(), done: false };
|
|
326
|
+
}
|
|
327
|
+
if (streamDone) {
|
|
328
|
+
return { value: void 0, done: true };
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const { done, value } = await reader.read();
|
|
332
|
+
if (done) {
|
|
333
|
+
self.streaming = false;
|
|
334
|
+
streamDone = true;
|
|
335
|
+
if (buffer.trim()) {
|
|
336
|
+
const chunk = parseSSELine(buffer.trim());
|
|
337
|
+
if (chunk) {
|
|
338
|
+
buffer = "";
|
|
339
|
+
return { value: chunk, done: false };
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
value: void 0,
|
|
344
|
+
done: true
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
buffer += decoder.decode(value, { stream: true });
|
|
348
|
+
const lines = buffer.split("\n");
|
|
349
|
+
buffer = lines.pop() || "";
|
|
350
|
+
for (const line of lines) {
|
|
351
|
+
const chunk = parseSSELine(line);
|
|
352
|
+
if (chunk) {
|
|
353
|
+
chunkQueue.push(chunk);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (chunkQueue.length > 0) {
|
|
357
|
+
return { value: chunkQueue.shift(), done: false };
|
|
358
|
+
}
|
|
359
|
+
return this.next();
|
|
360
|
+
} catch (error) {
|
|
361
|
+
self.streaming = false;
|
|
362
|
+
streamDone = true;
|
|
363
|
+
if (error.name === "AbortError") {
|
|
364
|
+
return {
|
|
365
|
+
value: void 0,
|
|
366
|
+
done: true
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/chat/classes/AbstractChat.ts
|
|
379
|
+
function buildToolResultContentForAI(result, tool, args) {
|
|
380
|
+
if (typeof result === "string") return result;
|
|
381
|
+
const typedResult = result;
|
|
382
|
+
const responseMode = typedResult?._aiResponseMode ?? tool?.aiResponseMode ?? "full";
|
|
383
|
+
if (typedResult?._aiContent) {
|
|
384
|
+
return JSON.stringify(typedResult._aiContent);
|
|
385
|
+
}
|
|
386
|
+
let aiContext = typedResult?._aiContext;
|
|
387
|
+
if (!aiContext && tool?.aiContext) {
|
|
388
|
+
aiContext = typeof tool.aiContext === "function" ? tool.aiContext(typedResult, args ?? {}) : tool.aiContext;
|
|
389
|
+
}
|
|
390
|
+
switch (responseMode) {
|
|
391
|
+
case "none":
|
|
392
|
+
return aiContext ?? "[Result displayed to user]";
|
|
393
|
+
case "brief":
|
|
394
|
+
return aiContext ?? "[Tool executed successfully]";
|
|
395
|
+
case "full":
|
|
396
|
+
default:
|
|
397
|
+
if (aiContext) {
|
|
398
|
+
const { _aiResponseMode, _aiContext, _aiContent, ...dataOnly } = typedResult ?? {};
|
|
399
|
+
return `${aiContext}
|
|
400
|
+
|
|
401
|
+
Full data: ${JSON.stringify(dataOnly)}`;
|
|
402
|
+
}
|
|
403
|
+
return JSON.stringify(result);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
var AbstractChat = class {
|
|
407
|
+
constructor(init) {
|
|
408
|
+
// Event handlers
|
|
409
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
410
|
+
// Current streaming state
|
|
411
|
+
this.streamState = null;
|
|
412
|
+
/**
|
|
413
|
+
* Dynamic context from useAIContext hook
|
|
414
|
+
*/
|
|
415
|
+
this.dynamicContext = "";
|
|
416
|
+
this.config = {
|
|
417
|
+
runtimeUrl: init.runtimeUrl,
|
|
418
|
+
llm: init.llm,
|
|
419
|
+
systemPrompt: init.systemPrompt,
|
|
420
|
+
streaming: init.streaming ?? true,
|
|
421
|
+
headers: init.headers,
|
|
422
|
+
threadId: init.threadId,
|
|
423
|
+
debug: init.debug
|
|
424
|
+
};
|
|
425
|
+
this.state = init.state ?? new SimpleChatState();
|
|
426
|
+
this.transport = init.transport ?? new HttpTransport({
|
|
427
|
+
url: init.runtimeUrl,
|
|
428
|
+
headers: init.headers,
|
|
429
|
+
streaming: init.streaming ?? true
|
|
430
|
+
});
|
|
431
|
+
this.callbacks = init.callbacks ?? {};
|
|
432
|
+
if (init.initialMessages?.length) {
|
|
433
|
+
this.state.setMessages(init.initialMessages);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// ============================================
|
|
437
|
+
// Public Getters
|
|
438
|
+
// ============================================
|
|
439
|
+
get messages() {
|
|
440
|
+
return this.state.messages;
|
|
441
|
+
}
|
|
442
|
+
get status() {
|
|
443
|
+
return this.state.status;
|
|
444
|
+
}
|
|
445
|
+
get error() {
|
|
446
|
+
return this.state.error;
|
|
447
|
+
}
|
|
448
|
+
get isStreaming() {
|
|
449
|
+
return this.transport.isStreaming();
|
|
450
|
+
}
|
|
451
|
+
// ============================================
|
|
452
|
+
// Public Actions
|
|
453
|
+
// ============================================
|
|
454
|
+
/**
|
|
455
|
+
* Send a message
|
|
456
|
+
*/
|
|
457
|
+
async sendMessage(content, attachments) {
|
|
458
|
+
this.debug("sendMessage", { content, attachments });
|
|
459
|
+
try {
|
|
460
|
+
const userMessage = createUserMessage(content, attachments);
|
|
461
|
+
this.state.pushMessage(userMessage);
|
|
462
|
+
this.state.status = "submitted";
|
|
463
|
+
this.state.error = void 0;
|
|
464
|
+
this.callbacks.onMessagesChange?.(this.state.messages);
|
|
465
|
+
this.callbacks.onStatusChange?.("submitted");
|
|
466
|
+
await Promise.resolve();
|
|
467
|
+
await this.processRequest();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.handleError(error);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Continue with tool results
|
|
474
|
+
*
|
|
475
|
+
* Automatically handles `addAsUserMessage` flag in results (e.g., screenshots).
|
|
476
|
+
* When a tool result has this flag, the attachment is extracted and sent as
|
|
477
|
+
* a user message so the AI can see it (e.g., for vision analysis).
|
|
478
|
+
*/
|
|
479
|
+
async continueWithToolResults(toolResults) {
|
|
480
|
+
this.debug("continueWithToolResults", toolResults);
|
|
481
|
+
try {
|
|
482
|
+
const attachmentsToAdd = [];
|
|
483
|
+
for (const { toolCallId, result } of toolResults) {
|
|
484
|
+
const typedResult = result;
|
|
485
|
+
let messageContent;
|
|
486
|
+
if (typedResult?.addAsUserMessage && typedResult.data?.attachment) {
|
|
487
|
+
this.debug(
|
|
488
|
+
"Tool result has attachment to add as user message",
|
|
489
|
+
typedResult.data.attachment.type
|
|
490
|
+
);
|
|
491
|
+
attachmentsToAdd.push(typedResult.data.attachment);
|
|
492
|
+
messageContent = JSON.stringify({
|
|
493
|
+
success: true,
|
|
494
|
+
message: typedResult.message || "Content shared in conversation."
|
|
495
|
+
});
|
|
496
|
+
} else {
|
|
497
|
+
messageContent = typeof result === "string" ? result : JSON.stringify(result);
|
|
498
|
+
}
|
|
499
|
+
const toolMessage = {
|
|
500
|
+
id: generateMessageId(),
|
|
501
|
+
role: "tool",
|
|
502
|
+
content: messageContent,
|
|
503
|
+
toolCallId,
|
|
504
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
505
|
+
};
|
|
506
|
+
this.state.pushMessage(toolMessage);
|
|
507
|
+
}
|
|
508
|
+
if (attachmentsToAdd.length > 0) {
|
|
509
|
+
this.debug(
|
|
510
|
+
"Adding user message with attachments",
|
|
511
|
+
attachmentsToAdd.length
|
|
512
|
+
);
|
|
513
|
+
const userMessage = {
|
|
514
|
+
id: generateMessageId(),
|
|
515
|
+
role: "user",
|
|
516
|
+
content: "Here's my screen:",
|
|
517
|
+
attachments: attachmentsToAdd,
|
|
518
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
519
|
+
};
|
|
520
|
+
this.state.pushMessage(userMessage);
|
|
521
|
+
}
|
|
522
|
+
this.state.status = "submitted";
|
|
523
|
+
this.callbacks.onMessagesChange?.(this.state.messages);
|
|
524
|
+
this.callbacks.onStatusChange?.("submitted");
|
|
525
|
+
await Promise.resolve();
|
|
526
|
+
await this.processRequest();
|
|
527
|
+
} catch (error) {
|
|
528
|
+
this.handleError(error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Stop generation
|
|
533
|
+
*/
|
|
534
|
+
stop() {
|
|
535
|
+
this.transport.abort();
|
|
536
|
+
this.state.status = "ready";
|
|
537
|
+
this.callbacks.onStatusChange?.("ready");
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Clear all messages
|
|
541
|
+
*/
|
|
542
|
+
clearMessages() {
|
|
543
|
+
this.state.clearMessages();
|
|
544
|
+
this.callbacks.onMessagesChange?.([]);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Set messages directly
|
|
548
|
+
*/
|
|
549
|
+
setMessages(messages) {
|
|
550
|
+
this.state.setMessages(messages);
|
|
551
|
+
this.callbacks.onMessagesChange?.(messages);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Regenerate last response
|
|
555
|
+
*/
|
|
556
|
+
async regenerate(messageId) {
|
|
557
|
+
const messages = this.state.messages;
|
|
558
|
+
let targetIndex = messages.length - 1;
|
|
559
|
+
if (messageId) {
|
|
560
|
+
targetIndex = messages.findIndex((m) => m.id === messageId);
|
|
561
|
+
} else {
|
|
562
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
563
|
+
if (messages[i].role === "assistant") {
|
|
564
|
+
targetIndex = i;
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (targetIndex > 0) {
|
|
570
|
+
this.state.setMessages(messages.slice(0, targetIndex));
|
|
571
|
+
this.callbacks.onMessagesChange?.(this.state.messages);
|
|
572
|
+
await this.processRequest();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// ============================================
|
|
576
|
+
// Event Handling
|
|
577
|
+
// ============================================
|
|
578
|
+
/**
|
|
579
|
+
* Subscribe to events
|
|
580
|
+
*/
|
|
581
|
+
on(event, handler) {
|
|
582
|
+
if (!this.eventHandlers.has(event)) {
|
|
583
|
+
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
584
|
+
}
|
|
585
|
+
this.eventHandlers.get(event).add(handler);
|
|
586
|
+
return () => {
|
|
587
|
+
this.eventHandlers.get(event)?.delete(handler);
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Emit an event
|
|
592
|
+
*/
|
|
593
|
+
emit(type, data) {
|
|
594
|
+
const event = { type, ...data };
|
|
595
|
+
this.eventHandlers.get(type)?.forEach((handler) => handler(event));
|
|
596
|
+
}
|
|
597
|
+
// ============================================
|
|
598
|
+
// Protected Methods
|
|
599
|
+
// ============================================
|
|
600
|
+
/**
|
|
601
|
+
* Process a chat request
|
|
602
|
+
*/
|
|
603
|
+
async processRequest() {
|
|
604
|
+
const request = this.buildRequest();
|
|
605
|
+
const response = await this.transport.send(request);
|
|
606
|
+
if (this.isAsyncIterable(response)) {
|
|
607
|
+
await this.handleStreamResponse(response);
|
|
608
|
+
} else {
|
|
609
|
+
this.handleJsonResponse(response);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Set tools available for the LLM
|
|
614
|
+
*/
|
|
615
|
+
setTools(tools) {
|
|
616
|
+
this.config.tools = tools;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Set dynamic context (appended to system prompt)
|
|
620
|
+
*/
|
|
621
|
+
setContext(context) {
|
|
622
|
+
this.dynamicContext = context;
|
|
623
|
+
this.debug("Context updated", { length: context.length });
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Build the request payload
|
|
627
|
+
*/
|
|
628
|
+
buildRequest() {
|
|
629
|
+
const tools = this.config.tools?.map((tool) => ({
|
|
630
|
+
name: tool.name,
|
|
631
|
+
description: tool.description,
|
|
632
|
+
inputSchema: tool.inputSchema
|
|
633
|
+
}));
|
|
634
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
635
|
+
for (const msg of this.state.messages) {
|
|
636
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
637
|
+
for (const tc of msg.toolCalls) {
|
|
638
|
+
try {
|
|
639
|
+
const args = tc.function?.arguments ? JSON.parse(tc.function.arguments) : {};
|
|
640
|
+
toolCallMap.set(tc.id, { toolName: tc.function.name, args });
|
|
641
|
+
} catch {
|
|
642
|
+
toolCallMap.set(tc.id, { toolName: tc.function.name, args: {} });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const toolDefMap = /* @__PURE__ */ new Map();
|
|
648
|
+
if (this.config.tools) {
|
|
649
|
+
for (const tool of this.config.tools) {
|
|
650
|
+
toolDefMap.set(tool.name, {
|
|
651
|
+
name: tool.name,
|
|
652
|
+
aiResponseMode: tool.aiResponseMode,
|
|
653
|
+
aiContext: tool.aiContext
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
messages: this.state.messages.map((m) => {
|
|
659
|
+
if (m.role === "tool" && m.content && m.toolCallId) {
|
|
660
|
+
try {
|
|
661
|
+
const fullResult = JSON.parse(m.content);
|
|
662
|
+
const toolCallInfo = toolCallMap.get(m.toolCallId);
|
|
663
|
+
const toolDef = toolCallInfo ? toolDefMap.get(toolCallInfo.toolName) : void 0;
|
|
664
|
+
const toolArgs = toolCallInfo?.args;
|
|
665
|
+
const transformedContent = buildToolResultContentForAI(
|
|
666
|
+
fullResult,
|
|
667
|
+
toolDef,
|
|
668
|
+
toolArgs
|
|
669
|
+
);
|
|
670
|
+
return {
|
|
671
|
+
role: m.role,
|
|
672
|
+
content: transformedContent,
|
|
673
|
+
tool_call_id: m.toolCallId
|
|
674
|
+
};
|
|
675
|
+
} catch (e) {
|
|
676
|
+
this.debug("Failed to parse tool message JSON", {
|
|
677
|
+
content: m.content?.slice(0, 100),
|
|
678
|
+
error: e instanceof Error ? e.message : String(e)
|
|
679
|
+
});
|
|
680
|
+
return {
|
|
681
|
+
role: m.role,
|
|
682
|
+
content: m.content,
|
|
683
|
+
tool_call_id: m.toolCallId
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
role: m.role,
|
|
689
|
+
content: m.content,
|
|
690
|
+
tool_calls: m.toolCalls,
|
|
691
|
+
tool_call_id: m.toolCallId,
|
|
692
|
+
attachments: m.attachments
|
|
693
|
+
};
|
|
694
|
+
}),
|
|
695
|
+
threadId: this.config.threadId,
|
|
696
|
+
systemPrompt: this.dynamicContext ? `${this.config.systemPrompt || ""}
|
|
697
|
+
|
|
698
|
+
## Current App Context:
|
|
699
|
+
${this.dynamicContext}`.trim() : this.config.systemPrompt,
|
|
700
|
+
llm: this.config.llm,
|
|
701
|
+
tools: tools?.length ? tools : void 0
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Handle streaming response
|
|
706
|
+
*/
|
|
707
|
+
async handleStreamResponse(stream) {
|
|
708
|
+
this.state.status = "streaming";
|
|
709
|
+
this.callbacks.onStatusChange?.("streaming");
|
|
710
|
+
const assistantMessage = createEmptyAssistantMessage();
|
|
711
|
+
this.state.pushMessage(assistantMessage);
|
|
712
|
+
this.streamState = createStreamState(assistantMessage.id);
|
|
713
|
+
this.callbacks.onMessageStart?.(assistantMessage.id);
|
|
714
|
+
this.debug("handleStreamResponse", "Starting to process stream");
|
|
715
|
+
let chunkCount = 0;
|
|
716
|
+
let toolCallsEmitted = false;
|
|
717
|
+
for await (const chunk of stream) {
|
|
718
|
+
chunkCount++;
|
|
719
|
+
this.debug("chunk", { count: chunkCount, type: chunk.type });
|
|
720
|
+
if (chunk.type === "error") {
|
|
721
|
+
const error = new Error(chunk.message || "Stream error");
|
|
722
|
+
this.handleError(error);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
this.streamState = processStreamChunk(chunk, this.streamState);
|
|
726
|
+
const updatedMessage = streamStateToMessage(this.streamState);
|
|
727
|
+
this.state.updateLastMessage(() => updatedMessage);
|
|
728
|
+
if (chunk.type === "message:delta") {
|
|
729
|
+
this.callbacks.onMessageDelta?.(assistantMessage.id, chunk.content);
|
|
730
|
+
}
|
|
731
|
+
if (requiresToolExecution(chunk) && !toolCallsEmitted) {
|
|
732
|
+
toolCallsEmitted = true;
|
|
733
|
+
this.debug("toolCalls", { toolCalls: updatedMessage.toolCalls });
|
|
734
|
+
this.emit("toolCalls", { toolCalls: updatedMessage.toolCalls });
|
|
735
|
+
}
|
|
736
|
+
if (isStreamDone(chunk)) {
|
|
737
|
+
this.debug("streamDone", { chunk });
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
this.debug("handleStreamResponse", `Processed ${chunkCount} chunks`);
|
|
742
|
+
const finalMessage = streamStateToMessage(this.streamState);
|
|
743
|
+
this.state.updateLastMessage(() => finalMessage);
|
|
744
|
+
this.state.status = "ready";
|
|
745
|
+
if (!finalMessage.content && (!finalMessage.toolCalls || finalMessage.toolCalls.length === 0)) {
|
|
746
|
+
this.debug("warning", "Empty response - no content and no tool calls");
|
|
747
|
+
}
|
|
748
|
+
this.callbacks.onMessageFinish?.(finalMessage);
|
|
749
|
+
this.callbacks.onStatusChange?.("ready");
|
|
750
|
+
this.callbacks.onMessagesChange?.(this.state.messages);
|
|
751
|
+
this.callbacks.onFinish?.(this.state.messages);
|
|
752
|
+
this.emit("done", {});
|
|
753
|
+
this.streamState = null;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Handle JSON (non-streaming) response
|
|
757
|
+
*/
|
|
758
|
+
handleJsonResponse(response) {
|
|
759
|
+
for (const msg of response.messages ?? []) {
|
|
760
|
+
const message = {
|
|
761
|
+
id: generateMessageId(),
|
|
762
|
+
role: msg.role,
|
|
763
|
+
content: msg.content ?? "",
|
|
764
|
+
toolCalls: msg.tool_calls,
|
|
765
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
766
|
+
};
|
|
767
|
+
this.state.pushMessage(message);
|
|
768
|
+
}
|
|
769
|
+
this.state.status = "ready";
|
|
770
|
+
this.callbacks.onStatusChange?.("ready");
|
|
771
|
+
this.callbacks.onMessagesChange?.(this.state.messages);
|
|
772
|
+
this.callbacks.onFinish?.(this.state.messages);
|
|
773
|
+
if (response.requiresAction && this.state.messages.length > 0) {
|
|
774
|
+
const lastMessage = this.state.messages[this.state.messages.length - 1];
|
|
775
|
+
if (lastMessage?.toolCalls?.length) {
|
|
776
|
+
this.emit("toolCalls", { toolCalls: lastMessage.toolCalls });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
this.emit("done", {});
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Handle errors
|
|
783
|
+
*/
|
|
784
|
+
handleError(error) {
|
|
785
|
+
this.debug("error", error);
|
|
786
|
+
this.state.error = error;
|
|
787
|
+
this.state.status = "error";
|
|
788
|
+
this.callbacks.onError?.(error);
|
|
789
|
+
this.callbacks.onStatusChange?.("error");
|
|
790
|
+
this.emit("error", { error });
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Debug logging
|
|
794
|
+
*/
|
|
795
|
+
debug(action, data) {
|
|
796
|
+
if (this.config.debug) {
|
|
797
|
+
console.log(`[AbstractChat] ${action}`, data);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Type guard for async iterable
|
|
802
|
+
*/
|
|
803
|
+
isAsyncIterable(value) {
|
|
804
|
+
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Dispose and cleanup
|
|
808
|
+
*/
|
|
809
|
+
dispose() {
|
|
810
|
+
this.stop();
|
|
811
|
+
this.eventHandlers.clear();
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/chat/AbstractAgentLoop.ts
|
|
816
|
+
var AbstractAgentLoop = class {
|
|
817
|
+
constructor(config = {}, callbacks = {}) {
|
|
818
|
+
// Internal state
|
|
819
|
+
this._toolExecutions = [];
|
|
820
|
+
this._iteration = 0;
|
|
821
|
+
this._maxIterationsReached = false;
|
|
822
|
+
this._isProcessing = false;
|
|
823
|
+
// Registered tools
|
|
824
|
+
this.registeredTools = /* @__PURE__ */ new Map();
|
|
825
|
+
// Pending approvals
|
|
826
|
+
this.pendingApprovals = /* @__PURE__ */ new Map();
|
|
827
|
+
this.config = config;
|
|
828
|
+
this.callbacks = callbacks;
|
|
829
|
+
this._maxIterations = config.maxIterations ?? 20;
|
|
830
|
+
this._maxExecutionHistory = config.maxExecutionHistory ?? 100;
|
|
831
|
+
if (config.tools) {
|
|
832
|
+
for (const tool of config.tools) {
|
|
833
|
+
this.registerTool(tool);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
// ============================================
|
|
838
|
+
// Getters
|
|
839
|
+
// ============================================
|
|
840
|
+
get toolExecutions() {
|
|
841
|
+
return this._toolExecutions;
|
|
842
|
+
}
|
|
843
|
+
get iteration() {
|
|
844
|
+
return this._iteration;
|
|
845
|
+
}
|
|
846
|
+
get maxIterations() {
|
|
847
|
+
return this._maxIterations;
|
|
848
|
+
}
|
|
849
|
+
get maxIterationsReached() {
|
|
850
|
+
return this._maxIterationsReached;
|
|
851
|
+
}
|
|
852
|
+
get isProcessing() {
|
|
853
|
+
return this._isProcessing;
|
|
854
|
+
}
|
|
855
|
+
get state() {
|
|
856
|
+
return {
|
|
857
|
+
toolExecutions: this._toolExecutions,
|
|
858
|
+
iteration: this._iteration,
|
|
859
|
+
maxIterations: this._maxIterations,
|
|
860
|
+
maxIterationsReached: this._maxIterationsReached,
|
|
861
|
+
isProcessing: this._isProcessing
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
get pendingApprovalExecutions() {
|
|
865
|
+
return this._toolExecutions.filter(
|
|
866
|
+
(exec) => exec.approvalStatus === "required"
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
get tools() {
|
|
870
|
+
return Array.from(this.registeredTools.values());
|
|
871
|
+
}
|
|
872
|
+
// ============================================
|
|
873
|
+
// Private setters with callbacks
|
|
874
|
+
// ============================================
|
|
875
|
+
setToolExecutions(executions) {
|
|
876
|
+
this._toolExecutions = executions;
|
|
877
|
+
this.callbacks.onExecutionsChange?.(executions);
|
|
878
|
+
}
|
|
879
|
+
setIteration(iteration) {
|
|
880
|
+
this._iteration = iteration;
|
|
881
|
+
}
|
|
882
|
+
setProcessing(processing) {
|
|
883
|
+
this._isProcessing = processing;
|
|
884
|
+
}
|
|
885
|
+
addToolExecution(execution) {
|
|
886
|
+
this._toolExecutions = [...this._toolExecutions, execution];
|
|
887
|
+
if (this._toolExecutions.length > this._maxExecutionHistory) {
|
|
888
|
+
this._toolExecutions = this._toolExecutions.slice(
|
|
889
|
+
-this._maxExecutionHistory
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
this.callbacks.onExecutionsChange?.(this._toolExecutions);
|
|
893
|
+
}
|
|
894
|
+
updateToolExecution(id, update) {
|
|
895
|
+
this._toolExecutions = this._toolExecutions.map(
|
|
896
|
+
(exec) => exec.id === id ? { ...exec, ...update } : exec
|
|
897
|
+
);
|
|
898
|
+
this.callbacks.onExecutionsChange?.(this._toolExecutions);
|
|
899
|
+
}
|
|
900
|
+
// ============================================
|
|
901
|
+
// Tool Registration
|
|
902
|
+
// ============================================
|
|
903
|
+
/**
|
|
904
|
+
* Register a tool
|
|
905
|
+
*/
|
|
906
|
+
registerTool(tool) {
|
|
907
|
+
this.registeredTools.set(tool.name, tool);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Unregister a tool
|
|
911
|
+
*/
|
|
912
|
+
unregisterTool(name) {
|
|
913
|
+
this.registeredTools.delete(name);
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Get a registered tool
|
|
917
|
+
*/
|
|
918
|
+
getTool(name) {
|
|
919
|
+
return this.registeredTools.get(name);
|
|
920
|
+
}
|
|
921
|
+
// ============================================
|
|
922
|
+
// Tool Execution
|
|
923
|
+
// ============================================
|
|
924
|
+
/**
|
|
925
|
+
* Execute tool calls from LLM response
|
|
926
|
+
* Returns tool results for sending back to LLM
|
|
927
|
+
*/
|
|
928
|
+
async executeToolCalls(toolCalls) {
|
|
929
|
+
if (this._iteration >= this._maxIterations) {
|
|
930
|
+
this._maxIterationsReached = true;
|
|
931
|
+
this.callbacks.onMaxIterationsReached?.();
|
|
932
|
+
return [];
|
|
933
|
+
}
|
|
934
|
+
this.setIteration(this._iteration + 1);
|
|
935
|
+
const results = [];
|
|
936
|
+
for (const toolCall of toolCalls) {
|
|
937
|
+
const result = await this.executeSingleTool(toolCall);
|
|
938
|
+
results.push(result);
|
|
939
|
+
}
|
|
940
|
+
return results;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Execute a single tool
|
|
944
|
+
*/
|
|
945
|
+
async executeSingleTool(toolCall) {
|
|
946
|
+
const tool = this.registeredTools.get(toolCall.name);
|
|
947
|
+
const execution = {
|
|
948
|
+
id: toolCall.id,
|
|
949
|
+
toolCallId: toolCall.id,
|
|
950
|
+
name: toolCall.name,
|
|
951
|
+
args: toolCall.args,
|
|
952
|
+
status: "pending",
|
|
953
|
+
approvalStatus: "none",
|
|
954
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
955
|
+
};
|
|
956
|
+
this.addToolExecution(execution);
|
|
957
|
+
this.callbacks.onToolStart?.(execution);
|
|
958
|
+
if (!tool) {
|
|
959
|
+
const errorResult = {
|
|
960
|
+
toolCallId: toolCall.id,
|
|
961
|
+
success: false,
|
|
962
|
+
error: `Tool "${toolCall.name}" not found`
|
|
963
|
+
};
|
|
964
|
+
this.updateToolExecution(toolCall.id, {
|
|
965
|
+
status: "failed",
|
|
966
|
+
error: errorResult.error,
|
|
967
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
968
|
+
});
|
|
969
|
+
return errorResult;
|
|
970
|
+
}
|
|
971
|
+
if (tool.needsApproval && !this.config.autoApprove) {
|
|
972
|
+
typeof tool.approvalMessage === "function" ? tool.approvalMessage(toolCall.args) : tool.approvalMessage;
|
|
973
|
+
execution.approvalStatus = "required";
|
|
974
|
+
this.updateToolExecution(toolCall.id, {
|
|
975
|
+
approvalStatus: "required"
|
|
976
|
+
});
|
|
977
|
+
this.callbacks.onApprovalRequired?.(execution);
|
|
978
|
+
const approved = await this.waitForApproval(toolCall.id, execution);
|
|
979
|
+
if (!approved) {
|
|
980
|
+
const rejectedResult = {
|
|
981
|
+
toolCallId: toolCall.id,
|
|
982
|
+
success: false,
|
|
983
|
+
error: "Tool execution was rejected by user"
|
|
984
|
+
};
|
|
985
|
+
this.updateToolExecution(toolCall.id, {
|
|
986
|
+
status: "rejected",
|
|
987
|
+
approvalStatus: "rejected",
|
|
988
|
+
error: rejectedResult.error,
|
|
989
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
990
|
+
});
|
|
991
|
+
return rejectedResult;
|
|
992
|
+
}
|
|
993
|
+
this.updateToolExecution(toolCall.id, {
|
|
994
|
+
approvalStatus: "approved"
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
this.updateToolExecution(toolCall.id, { status: "executing" });
|
|
998
|
+
try {
|
|
999
|
+
if (!tool.handler) {
|
|
1000
|
+
throw new Error(`Tool "${toolCall.name}" has no handler`);
|
|
1001
|
+
}
|
|
1002
|
+
const result = await tool.handler(toolCall.args, {
|
|
1003
|
+
data: { toolCallId: toolCall.id }
|
|
1004
|
+
});
|
|
1005
|
+
this.updateToolExecution(toolCall.id, {
|
|
1006
|
+
status: "completed",
|
|
1007
|
+
result,
|
|
1008
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
1009
|
+
});
|
|
1010
|
+
const updatedExecution = this._toolExecutions.find(
|
|
1011
|
+
(e) => e.id === toolCall.id
|
|
1012
|
+
);
|
|
1013
|
+
if (updatedExecution) {
|
|
1014
|
+
this.callbacks.onToolComplete?.(updatedExecution);
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
toolCallId: toolCall.id,
|
|
1018
|
+
success: true,
|
|
1019
|
+
result
|
|
1020
|
+
};
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1023
|
+
this.updateToolExecution(toolCall.id, {
|
|
1024
|
+
status: "failed",
|
|
1025
|
+
error: errorMessage,
|
|
1026
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
1027
|
+
});
|
|
1028
|
+
return {
|
|
1029
|
+
toolCallId: toolCall.id,
|
|
1030
|
+
success: false,
|
|
1031
|
+
error: errorMessage
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Wait for user approval
|
|
1037
|
+
*/
|
|
1038
|
+
waitForApproval(executionId, execution) {
|
|
1039
|
+
return new Promise((resolve) => {
|
|
1040
|
+
this.pendingApprovals.set(executionId, { resolve, execution });
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
// ============================================
|
|
1044
|
+
// Actions (implements AgentLoopActions)
|
|
1045
|
+
// ============================================
|
|
1046
|
+
/**
|
|
1047
|
+
* Approve a tool execution
|
|
1048
|
+
*/
|
|
1049
|
+
approveToolExecution(executionId, _permissionLevel) {
|
|
1050
|
+
const pending = this.pendingApprovals.get(executionId);
|
|
1051
|
+
if (pending) {
|
|
1052
|
+
pending.resolve(true);
|
|
1053
|
+
this.pendingApprovals.delete(executionId);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Reject a tool execution
|
|
1058
|
+
*/
|
|
1059
|
+
rejectToolExecution(executionId, reason, _permissionLevel) {
|
|
1060
|
+
const pending = this.pendingApprovals.get(executionId);
|
|
1061
|
+
if (pending) {
|
|
1062
|
+
if (reason) {
|
|
1063
|
+
this.updateToolExecution(executionId, {
|
|
1064
|
+
error: reason
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
pending.resolve(false);
|
|
1068
|
+
this.pendingApprovals.delete(executionId);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Clear all tool executions
|
|
1073
|
+
*/
|
|
1074
|
+
clearToolExecutions() {
|
|
1075
|
+
this.setToolExecutions([]);
|
|
1076
|
+
this.setIteration(0);
|
|
1077
|
+
this._maxIterationsReached = false;
|
|
1078
|
+
}
|
|
1079
|
+
// ============================================
|
|
1080
|
+
// State Management
|
|
1081
|
+
// ============================================
|
|
1082
|
+
/**
|
|
1083
|
+
* Reset the agent loop for a new conversation
|
|
1084
|
+
*/
|
|
1085
|
+
reset() {
|
|
1086
|
+
this.clearToolExecutions();
|
|
1087
|
+
this.pendingApprovals.clear();
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Update configuration
|
|
1091
|
+
*/
|
|
1092
|
+
updateConfig(config) {
|
|
1093
|
+
this.config = { ...this.config, ...config };
|
|
1094
|
+
if (config.maxIterations !== void 0) {
|
|
1095
|
+
this._maxIterations = config.maxIterations;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Update callbacks
|
|
1100
|
+
*/
|
|
1101
|
+
updateCallbacks(callbacks) {
|
|
1102
|
+
this.callbacks = { ...this.callbacks, ...callbacks };
|
|
1103
|
+
}
|
|
1104
|
+
// ============================================
|
|
1105
|
+
// Cleanup
|
|
1106
|
+
// ============================================
|
|
1107
|
+
/**
|
|
1108
|
+
* Dispose of resources
|
|
1109
|
+
*/
|
|
1110
|
+
dispose() {
|
|
1111
|
+
for (const [id, pending] of this.pendingApprovals) {
|
|
1112
|
+
pending.resolve(false);
|
|
1113
|
+
}
|
|
1114
|
+
this.pendingApprovals.clear();
|
|
1115
|
+
this.registeredTools.clear();
|
|
1116
|
+
this._toolExecutions = [];
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// src/chat/ChatWithTools.ts
|
|
1121
|
+
var ChatWithTools = class {
|
|
1122
|
+
constructor(config, callbacks = {}) {
|
|
1123
|
+
this.config = config;
|
|
1124
|
+
this.callbacks = callbacks;
|
|
1125
|
+
this.agentLoop = new AbstractAgentLoop(
|
|
1126
|
+
{
|
|
1127
|
+
maxIterations: config.maxIterations ?? 20,
|
|
1128
|
+
tools: config.tools
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
onExecutionsChange: (executions) => {
|
|
1132
|
+
callbacks.onToolExecutionsChange?.(executions);
|
|
1133
|
+
},
|
|
1134
|
+
onApprovalRequired: (execution) => {
|
|
1135
|
+
callbacks.onApprovalRequired?.(execution);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
this.chat = new AbstractChat({
|
|
1140
|
+
runtimeUrl: config.runtimeUrl,
|
|
1141
|
+
llm: config.llm,
|
|
1142
|
+
systemPrompt: config.systemPrompt,
|
|
1143
|
+
streaming: config.streaming,
|
|
1144
|
+
headers: config.headers,
|
|
1145
|
+
threadId: config.threadId,
|
|
1146
|
+
debug: config.debug,
|
|
1147
|
+
initialMessages: config.initialMessages,
|
|
1148
|
+
state: config.state,
|
|
1149
|
+
transport: config.transport,
|
|
1150
|
+
callbacks: {
|
|
1151
|
+
onMessagesChange: callbacks.onMessagesChange,
|
|
1152
|
+
onStatusChange: callbacks.onStatusChange,
|
|
1153
|
+
onError: callbacks.onError,
|
|
1154
|
+
onMessageStart: callbacks.onMessageStart,
|
|
1155
|
+
onMessageDelta: callbacks.onMessageDelta,
|
|
1156
|
+
onMessageFinish: callbacks.onMessageFinish,
|
|
1157
|
+
onToolCalls: callbacks.onToolCalls,
|
|
1158
|
+
onFinish: callbacks.onFinish
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
this.wireEvents();
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Wire up internal events between chat and agent loop
|
|
1165
|
+
*/
|
|
1166
|
+
wireEvents() {
|
|
1167
|
+
this.chat.on("toolCalls", async (event) => {
|
|
1168
|
+
const toolCalls = event.toolCalls;
|
|
1169
|
+
if (!toolCalls?.length) return;
|
|
1170
|
+
this.debug("Tool calls received:", toolCalls);
|
|
1171
|
+
const toolCallInfos = toolCalls.map((tc) => {
|
|
1172
|
+
const tcAny = tc;
|
|
1173
|
+
const name = tcAny.function?.name ?? tcAny.name ?? "";
|
|
1174
|
+
let args = {};
|
|
1175
|
+
if (tcAny.function?.arguments) {
|
|
1176
|
+
try {
|
|
1177
|
+
args = JSON.parse(tcAny.function.arguments);
|
|
1178
|
+
} catch {
|
|
1179
|
+
args = {};
|
|
1180
|
+
}
|
|
1181
|
+
} else if (tcAny.args) {
|
|
1182
|
+
args = tcAny.args;
|
|
1183
|
+
}
|
|
1184
|
+
return { id: tc.id, name, args };
|
|
1185
|
+
});
|
|
1186
|
+
try {
|
|
1187
|
+
const results = await this.agentLoop.executeToolCalls(toolCallInfos);
|
|
1188
|
+
this.debug("Tool results:", results);
|
|
1189
|
+
if (results.length > 0) {
|
|
1190
|
+
const toolResults = results.map((r) => ({
|
|
1191
|
+
toolCallId: r.toolCallId,
|
|
1192
|
+
result: r.success ? r.result : { success: false, error: r.error }
|
|
1193
|
+
}));
|
|
1194
|
+
await this.chat.continueWithToolResults(toolResults);
|
|
1195
|
+
}
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
this.debug("Error executing tools:", error);
|
|
1198
|
+
console.error("[ChatWithTools] Tool execution error:", error);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
// ============================================
|
|
1203
|
+
// Chat Getters
|
|
1204
|
+
// ============================================
|
|
1205
|
+
get messages() {
|
|
1206
|
+
return this.chat.messages;
|
|
1207
|
+
}
|
|
1208
|
+
get status() {
|
|
1209
|
+
return this.chat.status;
|
|
1210
|
+
}
|
|
1211
|
+
get error() {
|
|
1212
|
+
return this.chat.error;
|
|
1213
|
+
}
|
|
1214
|
+
get isStreaming() {
|
|
1215
|
+
return this.chat.isStreaming;
|
|
1216
|
+
}
|
|
1217
|
+
// ============================================
|
|
1218
|
+
// Tool Execution Getters
|
|
1219
|
+
// ============================================
|
|
1220
|
+
get toolExecutions() {
|
|
1221
|
+
return this.agentLoop.toolExecutions;
|
|
1222
|
+
}
|
|
1223
|
+
get tools() {
|
|
1224
|
+
return this.agentLoop.tools;
|
|
1225
|
+
}
|
|
1226
|
+
get iteration() {
|
|
1227
|
+
return this.agentLoop.iteration;
|
|
1228
|
+
}
|
|
1229
|
+
get maxIterations() {
|
|
1230
|
+
return this.agentLoop.maxIterations;
|
|
1231
|
+
}
|
|
1232
|
+
get isProcessing() {
|
|
1233
|
+
return this.agentLoop.isProcessing;
|
|
1234
|
+
}
|
|
1235
|
+
// ============================================
|
|
1236
|
+
// Chat Actions
|
|
1237
|
+
// ============================================
|
|
1238
|
+
/**
|
|
1239
|
+
* Send a message
|
|
1240
|
+
*/
|
|
1241
|
+
async sendMessage(content, attachments) {
|
|
1242
|
+
await this.chat.sendMessage(content, attachments);
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Stop generation
|
|
1246
|
+
*/
|
|
1247
|
+
stop() {
|
|
1248
|
+
this.chat.stop();
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Clear all messages
|
|
1252
|
+
*/
|
|
1253
|
+
clearMessages() {
|
|
1254
|
+
this.chat.clearMessages();
|
|
1255
|
+
this.agentLoop.clearToolExecutions();
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Set messages directly
|
|
1259
|
+
*/
|
|
1260
|
+
setMessages(messages) {
|
|
1261
|
+
this.chat.setMessages(messages);
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Regenerate last response
|
|
1265
|
+
*/
|
|
1266
|
+
async regenerate(messageId) {
|
|
1267
|
+
await this.chat.regenerate(messageId);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Set tools available for the LLM
|
|
1271
|
+
*/
|
|
1272
|
+
setTools(tools) {
|
|
1273
|
+
this.chat.setTools(tools);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Set dynamic context (from useAIContext hook)
|
|
1277
|
+
*/
|
|
1278
|
+
setContext(context) {
|
|
1279
|
+
this.chat.setContext(context);
|
|
1280
|
+
}
|
|
1281
|
+
// ============================================
|
|
1282
|
+
// Tool Registration
|
|
1283
|
+
// ============================================
|
|
1284
|
+
/**
|
|
1285
|
+
* Register a tool
|
|
1286
|
+
*/
|
|
1287
|
+
registerTool(tool) {
|
|
1288
|
+
this.agentLoop.registerTool(tool);
|
|
1289
|
+
this.chat.setTools(this.agentLoop.tools);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Unregister a tool
|
|
1293
|
+
*/
|
|
1294
|
+
unregisterTool(name) {
|
|
1295
|
+
this.agentLoop.unregisterTool(name);
|
|
1296
|
+
this.chat.setTools(this.agentLoop.tools);
|
|
1297
|
+
}
|
|
1298
|
+
// ============================================
|
|
1299
|
+
// Tool Approval
|
|
1300
|
+
// ============================================
|
|
1301
|
+
/**
|
|
1302
|
+
* Approve a tool execution
|
|
1303
|
+
*/
|
|
1304
|
+
approveToolExecution(id, permissionLevel) {
|
|
1305
|
+
this.agentLoop.approveToolExecution(id, permissionLevel);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Reject a tool execution
|
|
1309
|
+
*/
|
|
1310
|
+
rejectToolExecution(id, reason, permissionLevel) {
|
|
1311
|
+
this.agentLoop.rejectToolExecution(id, reason, permissionLevel);
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Clear tool executions
|
|
1315
|
+
*/
|
|
1316
|
+
clearToolExecutions() {
|
|
1317
|
+
this.agentLoop.clearToolExecutions();
|
|
1318
|
+
}
|
|
1319
|
+
// ============================================
|
|
1320
|
+
// Event Subscriptions (for framework adapters)
|
|
1321
|
+
// ============================================
|
|
1322
|
+
/**
|
|
1323
|
+
* Subscribe to chat events
|
|
1324
|
+
*/
|
|
1325
|
+
on(event, handler) {
|
|
1326
|
+
return this.chat.on(event, handler);
|
|
1327
|
+
}
|
|
1328
|
+
// ============================================
|
|
1329
|
+
// Cleanup
|
|
1330
|
+
// ============================================
|
|
1331
|
+
/**
|
|
1332
|
+
* Dispose and cleanup
|
|
1333
|
+
*/
|
|
1334
|
+
dispose() {
|
|
1335
|
+
this.chat.dispose();
|
|
1336
|
+
this.agentLoop.dispose();
|
|
1337
|
+
}
|
|
1338
|
+
// ============================================
|
|
1339
|
+
// Private
|
|
1340
|
+
// ============================================
|
|
1341
|
+
debug(message, ...args) {
|
|
1342
|
+
if (this.config.debug) {
|
|
1343
|
+
console.log(`[ChatWithTools] ${message}`, ...args);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
// src/react/internal/ReactChatState.ts
|
|
1349
|
+
var ReactChatState = class {
|
|
1350
|
+
constructor(initialMessages) {
|
|
1351
|
+
this._messages = [];
|
|
1352
|
+
this._status = "ready";
|
|
1353
|
+
this._error = void 0;
|
|
1354
|
+
// Callbacks for React subscriptions (useSyncExternalStore)
|
|
1355
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
1356
|
+
// ============================================
|
|
1357
|
+
// Subscription (for useSyncExternalStore)
|
|
1358
|
+
// ============================================
|
|
1359
|
+
/**
|
|
1360
|
+
* Subscribe to state changes.
|
|
1361
|
+
* Returns an unsubscribe function.
|
|
1362
|
+
*
|
|
1363
|
+
* @example
|
|
1364
|
+
* ```tsx
|
|
1365
|
+
* const messages = useSyncExternalStore(
|
|
1366
|
+
* state.subscribe,
|
|
1367
|
+
* () => state.messages
|
|
1368
|
+
* );
|
|
1369
|
+
* ```
|
|
1370
|
+
*/
|
|
1371
|
+
this.subscribe = (callback) => {
|
|
1372
|
+
this.subscribers.add(callback);
|
|
1373
|
+
return () => {
|
|
1374
|
+
this.subscribers.delete(callback);
|
|
1375
|
+
};
|
|
1376
|
+
};
|
|
1377
|
+
if (initialMessages) {
|
|
1378
|
+
this._messages = initialMessages;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
// ============================================
|
|
1382
|
+
// Getters
|
|
1383
|
+
// ============================================
|
|
1384
|
+
get messages() {
|
|
1385
|
+
return this._messages;
|
|
1386
|
+
}
|
|
1387
|
+
get status() {
|
|
1388
|
+
return this._status;
|
|
1389
|
+
}
|
|
1390
|
+
get error() {
|
|
1391
|
+
return this._error;
|
|
1392
|
+
}
|
|
1393
|
+
// ============================================
|
|
1394
|
+
// Setters (trigger reactivity)
|
|
1395
|
+
// ============================================
|
|
1396
|
+
set messages(value) {
|
|
1397
|
+
this._messages = value;
|
|
1398
|
+
this.notify();
|
|
1399
|
+
}
|
|
1400
|
+
set status(value) {
|
|
1401
|
+
this._status = value;
|
|
1402
|
+
this.notify();
|
|
1403
|
+
}
|
|
1404
|
+
set error(value) {
|
|
1405
|
+
this._error = value;
|
|
1406
|
+
this.notify();
|
|
1407
|
+
}
|
|
1408
|
+
// ============================================
|
|
1409
|
+
// Mutations
|
|
1410
|
+
// ============================================
|
|
1411
|
+
pushMessage(message) {
|
|
1412
|
+
this._messages = [...this._messages, message];
|
|
1413
|
+
this.notify();
|
|
1414
|
+
}
|
|
1415
|
+
popMessage() {
|
|
1416
|
+
this._messages = this._messages.slice(0, -1);
|
|
1417
|
+
this.notify();
|
|
1418
|
+
}
|
|
1419
|
+
replaceMessage(index, message) {
|
|
1420
|
+
this._messages = this._messages.map((m, i) => i === index ? message : m);
|
|
1421
|
+
this.notify();
|
|
1422
|
+
}
|
|
1423
|
+
updateLastMessage(updater) {
|
|
1424
|
+
if (this._messages.length === 0) return;
|
|
1425
|
+
const lastIndex = this._messages.length - 1;
|
|
1426
|
+
const lastMessage = this._messages[lastIndex];
|
|
1427
|
+
this._messages = [
|
|
1428
|
+
...this._messages.slice(0, lastIndex),
|
|
1429
|
+
updater(lastMessage)
|
|
1430
|
+
];
|
|
1431
|
+
this.notify();
|
|
1432
|
+
}
|
|
1433
|
+
setMessages(messages) {
|
|
1434
|
+
this._messages = messages;
|
|
1435
|
+
this.notify();
|
|
1436
|
+
}
|
|
1437
|
+
clearMessages() {
|
|
1438
|
+
this._messages = [];
|
|
1439
|
+
this.notify();
|
|
1440
|
+
}
|
|
1441
|
+
// ============================================
|
|
1442
|
+
// Private Methods
|
|
1443
|
+
// ============================================
|
|
1444
|
+
notify() {
|
|
1445
|
+
this.subscribers.forEach((cb) => cb());
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Cleanup subscriptions
|
|
1449
|
+
*/
|
|
1450
|
+
dispose() {
|
|
1451
|
+
this.subscribers.clear();
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
function createReactChatState(initialMessages) {
|
|
1455
|
+
return new ReactChatState(initialMessages);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/react/internal/ReactChatWithTools.ts
|
|
1459
|
+
var ReactChatWithTools = class extends ChatWithTools {
|
|
1460
|
+
constructor(config, callbacks = {}) {
|
|
1461
|
+
const reactState = new ReactChatState(config.initialMessages);
|
|
1462
|
+
super({ ...config, state: reactState }, callbacks);
|
|
1463
|
+
/**
|
|
1464
|
+
* Subscribe to state changes (for useSyncExternalStore)
|
|
1465
|
+
*/
|
|
1466
|
+
this.subscribe = (callback) => {
|
|
1467
|
+
return this.reactState.subscribe(callback);
|
|
1468
|
+
};
|
|
1469
|
+
this.reactState = reactState;
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Dispose and cleanup
|
|
1473
|
+
*/
|
|
1474
|
+
dispose() {
|
|
1475
|
+
super.dispose();
|
|
1476
|
+
this.reactState.dispose();
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// src/react/utils/context-tree.ts
|
|
1481
|
+
function addNode(tree, node, parentId) {
|
|
1482
|
+
const newNode = {
|
|
1483
|
+
...node,
|
|
1484
|
+
children: node.children || []
|
|
1485
|
+
};
|
|
1486
|
+
if (!parentId) {
|
|
1487
|
+
return [...tree, newNode];
|
|
1488
|
+
}
|
|
1489
|
+
return tree.map((n) => {
|
|
1490
|
+
if (n.id === parentId) {
|
|
1491
|
+
return { ...n, children: [...n.children, newNode] };
|
|
1492
|
+
}
|
|
1493
|
+
if (n.children.length > 0) {
|
|
1494
|
+
return { ...n, children: addNode(n.children, node, parentId) };
|
|
1495
|
+
}
|
|
1496
|
+
return n;
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function removeNode(tree, id) {
|
|
1500
|
+
return tree.reduce((result, node) => {
|
|
1501
|
+
if (node.id !== id) {
|
|
1502
|
+
const newNode = { ...node, children: removeNode(node.children, id) };
|
|
1503
|
+
result.push(newNode);
|
|
1504
|
+
}
|
|
1505
|
+
return result;
|
|
1506
|
+
}, []);
|
|
1507
|
+
}
|
|
1508
|
+
function getIndentPrefix(index, level) {
|
|
1509
|
+
if (level === 0) {
|
|
1510
|
+
return `${index + 1}.`;
|
|
1511
|
+
} else if (level === 1) {
|
|
1512
|
+
return `${String.fromCharCode(65 + index)}.`;
|
|
1513
|
+
} else if (level === 2) {
|
|
1514
|
+
return `${String.fromCharCode(97 + index)}.`;
|
|
1515
|
+
} else {
|
|
1516
|
+
return "-";
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
function printNode(node, prefix = "", indentLevel = 0) {
|
|
1520
|
+
const indent = " ".repeat(indentLevel);
|
|
1521
|
+
const prefixLength = prefix.length + indent.length;
|
|
1522
|
+
const subsequentIndent = " ".repeat(prefixLength);
|
|
1523
|
+
const lines = node.value.split("\n");
|
|
1524
|
+
const firstLine = `${indent}${prefix}${lines[0]}`;
|
|
1525
|
+
const subsequentLines = lines.slice(1).map((line) => `${subsequentIndent}${line}`).join("\n");
|
|
1526
|
+
let output = `${firstLine}
|
|
1527
|
+
`;
|
|
1528
|
+
if (subsequentLines) {
|
|
1529
|
+
output += `${subsequentLines}
|
|
1530
|
+
`;
|
|
1531
|
+
}
|
|
1532
|
+
node.children.forEach((child, index) => {
|
|
1533
|
+
const childPrefix = `${" ".repeat(prefix.length)}${getIndentPrefix(index, indentLevel + 1)} `;
|
|
1534
|
+
output += printNode(child, childPrefix, indentLevel + 1);
|
|
1535
|
+
});
|
|
1536
|
+
return output;
|
|
1537
|
+
}
|
|
1538
|
+
function printTree(tree) {
|
|
1539
|
+
if (tree.length === 0) {
|
|
1540
|
+
return "";
|
|
1541
|
+
}
|
|
1542
|
+
let output = "";
|
|
1543
|
+
tree.forEach((node, index) => {
|
|
1544
|
+
if (index > 0) {
|
|
1545
|
+
output += "\n";
|
|
1546
|
+
}
|
|
1547
|
+
output += printNode(node, `${getIndentPrefix(index, 0)} `);
|
|
1548
|
+
});
|
|
1549
|
+
return output.trim();
|
|
1550
|
+
}
|
|
1551
|
+
var CopilotContext = createContext(null);
|
|
1552
|
+
function useCopilot() {
|
|
1553
|
+
const context = useContext(CopilotContext);
|
|
1554
|
+
if (!context) {
|
|
1555
|
+
throw new Error("useCopilot must be used within CopilotProvider");
|
|
1556
|
+
}
|
|
1557
|
+
return context;
|
|
1558
|
+
}
|
|
1559
|
+
function CopilotProvider({
|
|
1560
|
+
children,
|
|
1561
|
+
runtimeUrl,
|
|
1562
|
+
config,
|
|
1563
|
+
cloud,
|
|
1564
|
+
systemPrompt,
|
|
1565
|
+
tools: toolsConfig,
|
|
1566
|
+
threadId,
|
|
1567
|
+
initialMessages,
|
|
1568
|
+
onMessagesChange,
|
|
1569
|
+
onError,
|
|
1570
|
+
streaming,
|
|
1571
|
+
debug = false
|
|
1572
|
+
}) {
|
|
1573
|
+
const debugLog = useCallback(
|
|
1574
|
+
(...args) => {
|
|
1575
|
+
if (debug) console.log("[Copilot SDK]", ...args);
|
|
1576
|
+
},
|
|
1577
|
+
[debug]
|
|
1578
|
+
);
|
|
1579
|
+
useEffect(() => {
|
|
1580
|
+
if (toolsConfig && (toolsConfig.screenshot || toolsConfig.console || toolsConfig.network)) {
|
|
1581
|
+
console.warn(
|
|
1582
|
+
"[Copilot SDK] The `tools` prop is deprecated. Use the `useTools` hook instead."
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
}, [toolsConfig]);
|
|
1586
|
+
const [toolExecutions, setToolExecutions] = useState([]);
|
|
1587
|
+
const chatRef = useRef(null);
|
|
1588
|
+
if (chatRef.current === null) {
|
|
1589
|
+
const uiInitialMessages = initialMessages?.map(
|
|
1590
|
+
(m) => ({
|
|
1591
|
+
id: m.id,
|
|
1592
|
+
role: m.role,
|
|
1593
|
+
content: m.content ?? "",
|
|
1594
|
+
createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
|
|
1595
|
+
attachments: m.metadata?.attachments,
|
|
1596
|
+
toolCalls: m.tool_calls,
|
|
1597
|
+
toolCallId: m.tool_call_id
|
|
1598
|
+
})
|
|
1599
|
+
);
|
|
1600
|
+
chatRef.current = new ReactChatWithTools(
|
|
1601
|
+
{
|
|
1602
|
+
runtimeUrl,
|
|
1603
|
+
llm: config,
|
|
1604
|
+
systemPrompt,
|
|
1605
|
+
threadId,
|
|
1606
|
+
initialMessages: uiInitialMessages,
|
|
1607
|
+
streaming,
|
|
1608
|
+
debug
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
onToolExecutionsChange: (executions) => {
|
|
1612
|
+
debugLog("Tool executions changed:", executions.length);
|
|
1613
|
+
setToolExecutions(executions);
|
|
1614
|
+
},
|
|
1615
|
+
onApprovalRequired: (execution) => {
|
|
1616
|
+
debugLog("Tool approval required:", execution.name);
|
|
1617
|
+
},
|
|
1618
|
+
onError: (error2) => {
|
|
1619
|
+
if (error2) onError?.(error2);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
const messages = useSyncExternalStore(
|
|
1625
|
+
chatRef.current.subscribe,
|
|
1626
|
+
() => chatRef.current.messages,
|
|
1627
|
+
() => chatRef.current.messages
|
|
1628
|
+
);
|
|
1629
|
+
const status = useSyncExternalStore(
|
|
1630
|
+
chatRef.current.subscribe,
|
|
1631
|
+
() => chatRef.current.status,
|
|
1632
|
+
() => "ready"
|
|
1633
|
+
);
|
|
1634
|
+
const errorFromChat = useSyncExternalStore(
|
|
1635
|
+
chatRef.current.subscribe,
|
|
1636
|
+
() => chatRef.current.error,
|
|
1637
|
+
() => void 0
|
|
1638
|
+
);
|
|
1639
|
+
const error = errorFromChat ?? null;
|
|
1640
|
+
const isLoading = status === "streaming" || status === "submitted";
|
|
1641
|
+
const registerTool = useCallback((tool) => {
|
|
1642
|
+
chatRef.current?.registerTool(tool);
|
|
1643
|
+
}, []);
|
|
1644
|
+
const unregisterTool = useCallback((name) => {
|
|
1645
|
+
chatRef.current?.unregisterTool(name);
|
|
1646
|
+
}, []);
|
|
1647
|
+
const approveToolExecution = useCallback(
|
|
1648
|
+
(id, permissionLevel) => {
|
|
1649
|
+
chatRef.current?.approveToolExecution(id, permissionLevel);
|
|
1650
|
+
},
|
|
1651
|
+
[]
|
|
1652
|
+
);
|
|
1653
|
+
const rejectToolExecution = useCallback(
|
|
1654
|
+
(id, reason, permissionLevel) => {
|
|
1655
|
+
chatRef.current?.rejectToolExecution(id, reason, permissionLevel);
|
|
1656
|
+
},
|
|
1657
|
+
[]
|
|
1658
|
+
);
|
|
1659
|
+
const registeredTools = chatRef.current?.tools ?? [];
|
|
1660
|
+
const pendingApprovals = toolExecutions.filter(
|
|
1661
|
+
(e) => e.approvalStatus === "required"
|
|
1662
|
+
);
|
|
1663
|
+
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
1664
|
+
const [actionsVersion, setActionsVersion] = useState(0);
|
|
1665
|
+
const registerAction = useCallback((action) => {
|
|
1666
|
+
actionsRef.current.set(action.name, action);
|
|
1667
|
+
setActionsVersion((v) => v + 1);
|
|
1668
|
+
}, []);
|
|
1669
|
+
const unregisterAction = useCallback((name) => {
|
|
1670
|
+
actionsRef.current.delete(name);
|
|
1671
|
+
setActionsVersion((v) => v + 1);
|
|
1672
|
+
}, []);
|
|
1673
|
+
const registeredActions = useMemo(
|
|
1674
|
+
() => Array.from(actionsRef.current.values()),
|
|
1675
|
+
[actionsVersion]
|
|
1676
|
+
);
|
|
1677
|
+
const contextTreeRef = useRef([]);
|
|
1678
|
+
const contextIdCounter = useRef(0);
|
|
1679
|
+
const addContext = useCallback(
|
|
1680
|
+
(context, parentId) => {
|
|
1681
|
+
const id = `ctx-${++contextIdCounter.current}`;
|
|
1682
|
+
contextTreeRef.current = addNode(
|
|
1683
|
+
contextTreeRef.current,
|
|
1684
|
+
{ id, value: context, parentId },
|
|
1685
|
+
parentId
|
|
1686
|
+
);
|
|
1687
|
+
const contextString = printTree(contextTreeRef.current);
|
|
1688
|
+
chatRef.current?.setContext(contextString);
|
|
1689
|
+
debugLog("Context added:", id);
|
|
1690
|
+
return id;
|
|
1691
|
+
},
|
|
1692
|
+
[debugLog]
|
|
1693
|
+
);
|
|
1694
|
+
const removeContext = useCallback(
|
|
1695
|
+
(id) => {
|
|
1696
|
+
contextTreeRef.current = removeNode(contextTreeRef.current, id);
|
|
1697
|
+
const contextString = printTree(contextTreeRef.current);
|
|
1698
|
+
chatRef.current?.setContext(contextString);
|
|
1699
|
+
debugLog("Context removed:", id);
|
|
1700
|
+
},
|
|
1701
|
+
[debugLog]
|
|
1702
|
+
);
|
|
1703
|
+
const sendMessage = useCallback(
|
|
1704
|
+
async (content, attachments) => {
|
|
1705
|
+
debugLog("Sending message:", content);
|
|
1706
|
+
await chatRef.current?.sendMessage(content, attachments);
|
|
1707
|
+
},
|
|
1708
|
+
[debugLog]
|
|
1709
|
+
);
|
|
1710
|
+
const stop = useCallback(() => {
|
|
1711
|
+
chatRef.current?.stop();
|
|
1712
|
+
}, []);
|
|
1713
|
+
const clearMessages = useCallback(() => {
|
|
1714
|
+
chatRef.current?.clearMessages();
|
|
1715
|
+
}, []);
|
|
1716
|
+
const regenerate = useCallback(async (messageId) => {
|
|
1717
|
+
await chatRef.current?.regenerate(messageId);
|
|
1718
|
+
}, []);
|
|
1719
|
+
useEffect(() => {
|
|
1720
|
+
if (onMessagesChange && messages.length > 0) {
|
|
1721
|
+
const coreMessages = messages.map((m) => ({
|
|
1722
|
+
id: m.id,
|
|
1723
|
+
role: m.role,
|
|
1724
|
+
content: m.content,
|
|
1725
|
+
created_at: m.createdAt,
|
|
1726
|
+
tool_calls: m.toolCalls,
|
|
1727
|
+
tool_call_id: m.toolCallId,
|
|
1728
|
+
metadata: {
|
|
1729
|
+
attachments: m.attachments,
|
|
1730
|
+
thinking: m.thinking
|
|
1731
|
+
}
|
|
1732
|
+
}));
|
|
1733
|
+
onMessagesChange(coreMessages);
|
|
1734
|
+
}
|
|
1735
|
+
}, [messages, onMessagesChange]);
|
|
1736
|
+
useEffect(() => {
|
|
1737
|
+
if (error && onError) {
|
|
1738
|
+
onError(error);
|
|
1739
|
+
}
|
|
1740
|
+
}, [error, onError]);
|
|
1741
|
+
useEffect(() => {
|
|
1742
|
+
return () => {
|
|
1743
|
+
chatRef.current?.dispose();
|
|
1744
|
+
};
|
|
1745
|
+
}, []);
|
|
1746
|
+
const contextValue = useMemo(
|
|
1747
|
+
() => ({
|
|
1748
|
+
// Chat state
|
|
1749
|
+
messages,
|
|
1750
|
+
status,
|
|
1751
|
+
error,
|
|
1752
|
+
isLoading,
|
|
1753
|
+
// Chat actions
|
|
1754
|
+
sendMessage,
|
|
1755
|
+
stop,
|
|
1756
|
+
clearMessages,
|
|
1757
|
+
regenerate,
|
|
1758
|
+
// Tool execution
|
|
1759
|
+
registerTool,
|
|
1760
|
+
unregisterTool,
|
|
1761
|
+
registeredTools,
|
|
1762
|
+
toolExecutions,
|
|
1763
|
+
pendingApprovals,
|
|
1764
|
+
approveToolExecution,
|
|
1765
|
+
rejectToolExecution,
|
|
1766
|
+
// Actions
|
|
1767
|
+
registerAction,
|
|
1768
|
+
unregisterAction,
|
|
1769
|
+
registeredActions,
|
|
1770
|
+
// AI Context
|
|
1771
|
+
addContext,
|
|
1772
|
+
removeContext,
|
|
1773
|
+
// Config
|
|
1774
|
+
threadId,
|
|
1775
|
+
runtimeUrl,
|
|
1776
|
+
toolsConfig
|
|
1777
|
+
}),
|
|
1778
|
+
[
|
|
1779
|
+
messages,
|
|
1780
|
+
status,
|
|
1781
|
+
error,
|
|
1782
|
+
isLoading,
|
|
1783
|
+
sendMessage,
|
|
1784
|
+
stop,
|
|
1785
|
+
clearMessages,
|
|
1786
|
+
regenerate,
|
|
1787
|
+
registerTool,
|
|
1788
|
+
unregisterTool,
|
|
1789
|
+
registeredTools,
|
|
1790
|
+
toolExecutions,
|
|
1791
|
+
pendingApprovals,
|
|
1792
|
+
approveToolExecution,
|
|
1793
|
+
rejectToolExecution,
|
|
1794
|
+
registerAction,
|
|
1795
|
+
unregisterAction,
|
|
1796
|
+
registeredActions,
|
|
1797
|
+
addContext,
|
|
1798
|
+
removeContext,
|
|
1799
|
+
threadId,
|
|
1800
|
+
runtimeUrl,
|
|
1801
|
+
toolsConfig
|
|
1802
|
+
]
|
|
1803
|
+
);
|
|
1804
|
+
return /* @__PURE__ */ jsx(CopilotContext.Provider, { value: contextValue, children });
|
|
1805
|
+
}
|
|
1806
|
+
function useAIActions(actions) {
|
|
1807
|
+
const { registerAction, unregisterAction } = useCopilot();
|
|
1808
|
+
useEffect(() => {
|
|
1809
|
+
for (const action of actions) {
|
|
1810
|
+
registerAction(action);
|
|
1811
|
+
}
|
|
1812
|
+
return () => {
|
|
1813
|
+
for (const action of actions) {
|
|
1814
|
+
unregisterAction(action.name);
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
}, [actions, registerAction, unregisterAction]);
|
|
1818
|
+
}
|
|
1819
|
+
function useAIAction(action) {
|
|
1820
|
+
useAIActions([action]);
|
|
1821
|
+
}
|
|
1822
|
+
function useAIContext(item) {
|
|
1823
|
+
const { addContext, removeContext } = useCopilot();
|
|
1824
|
+
const contextIdRef = useRef(null);
|
|
1825
|
+
const serializedData = typeof item.data === "string" ? item.data : JSON.stringify(item.data);
|
|
1826
|
+
useEffect(() => {
|
|
1827
|
+
const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
|
|
1828
|
+
const contextString = item.description ? `${item.description}:
|
|
1829
|
+
${formattedValue}` : `${item.key}:
|
|
1830
|
+
${formattedValue}`;
|
|
1831
|
+
contextIdRef.current = addContext(contextString, item.parentId);
|
|
1832
|
+
return () => {
|
|
1833
|
+
if (contextIdRef.current) {
|
|
1834
|
+
removeContext(contextIdRef.current);
|
|
1835
|
+
contextIdRef.current = null;
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
}, [
|
|
1839
|
+
item.key,
|
|
1840
|
+
serializedData,
|
|
1841
|
+
item.description,
|
|
1842
|
+
item.parentId,
|
|
1843
|
+
addContext,
|
|
1844
|
+
removeContext
|
|
1845
|
+
]);
|
|
1846
|
+
return contextIdRef.current ?? void 0;
|
|
1847
|
+
}
|
|
1848
|
+
function useAIContexts(items) {
|
|
1849
|
+
const { addContext, removeContext } = useCopilot();
|
|
1850
|
+
const contextIdsRef = useRef([]);
|
|
1851
|
+
const serializedItems = JSON.stringify(
|
|
1852
|
+
items.map((item) => ({
|
|
1853
|
+
key: item.key,
|
|
1854
|
+
data: item.data,
|
|
1855
|
+
description: item.description,
|
|
1856
|
+
parentId: item.parentId
|
|
1857
|
+
}))
|
|
1858
|
+
);
|
|
1859
|
+
useEffect(() => {
|
|
1860
|
+
contextIdsRef.current.forEach((id) => removeContext(id));
|
|
1861
|
+
contextIdsRef.current = [];
|
|
1862
|
+
const parsedItems = JSON.parse(serializedItems);
|
|
1863
|
+
for (const item of parsedItems) {
|
|
1864
|
+
const formattedValue = typeof item.data === "string" ? item.data : JSON.stringify(item.data, null, 2);
|
|
1865
|
+
const contextString = item.description ? `${item.description}:
|
|
1866
|
+
${formattedValue}` : `${item.key}:
|
|
1867
|
+
${formattedValue}`;
|
|
1868
|
+
const id = addContext(contextString, item.parentId);
|
|
1869
|
+
contextIdsRef.current.push(id);
|
|
1870
|
+
}
|
|
1871
|
+
return () => {
|
|
1872
|
+
contextIdsRef.current.forEach((id) => removeContext(id));
|
|
1873
|
+
contextIdsRef.current = [];
|
|
1874
|
+
};
|
|
1875
|
+
}, [serializedItems, addContext, removeContext]);
|
|
1876
|
+
}
|
|
1877
|
+
function useAITools(options = {}) {
|
|
1878
|
+
const {
|
|
1879
|
+
screenshot = false,
|
|
1880
|
+
console: consoleCapture = false,
|
|
1881
|
+
network = false,
|
|
1882
|
+
requireConsent = true,
|
|
1883
|
+
screenshotOptions,
|
|
1884
|
+
consoleOptions,
|
|
1885
|
+
networkOptions,
|
|
1886
|
+
onConsentRequest,
|
|
1887
|
+
autoStart = true
|
|
1888
|
+
} = options;
|
|
1889
|
+
const [isEnabled] = useState(screenshot || consoleCapture || network);
|
|
1890
|
+
const [activeCaptures, setActiveCaptures] = useState({
|
|
1891
|
+
console: false,
|
|
1892
|
+
network: false
|
|
1893
|
+
});
|
|
1894
|
+
const [pendingConsent, setPendingConsent] = useState(null);
|
|
1895
|
+
const consentResolverRef = useRef(null);
|
|
1896
|
+
const rememberedConsentRef = useRef(/* @__PURE__ */ new Set());
|
|
1897
|
+
useEffect(() => {
|
|
1898
|
+
if (!autoStart || !isEnabled) return;
|
|
1899
|
+
if (consoleCapture && !isConsoleCaptureActive()) {
|
|
1900
|
+
startConsoleCapture(consoleOptions);
|
|
1901
|
+
setActiveCaptures((prev) => ({ ...prev, console: true }));
|
|
1902
|
+
}
|
|
1903
|
+
if (network && !isNetworkCaptureActive()) {
|
|
1904
|
+
startNetworkCapture(networkOptions);
|
|
1905
|
+
setActiveCaptures((prev) => ({ ...prev, network: true }));
|
|
1906
|
+
}
|
|
1907
|
+
return () => {
|
|
1908
|
+
stopConsoleCapture();
|
|
1909
|
+
stopNetworkCapture();
|
|
1910
|
+
};
|
|
1911
|
+
}, [
|
|
1912
|
+
autoStart,
|
|
1913
|
+
isEnabled,
|
|
1914
|
+
consoleCapture,
|
|
1915
|
+
network,
|
|
1916
|
+
consoleOptions,
|
|
1917
|
+
networkOptions
|
|
1918
|
+
]);
|
|
1919
|
+
const captureScreenshotFn = useCallback(
|
|
1920
|
+
async (opts) => {
|
|
1921
|
+
if (!screenshot) {
|
|
1922
|
+
throw new Error("Screenshot capture is not enabled");
|
|
1923
|
+
}
|
|
1924
|
+
if (!isScreenshotSupported()) {
|
|
1925
|
+
throw new Error(
|
|
1926
|
+
"Screenshot capture is not supported in this environment"
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
return captureScreenshot({ ...screenshotOptions, ...opts });
|
|
1930
|
+
},
|
|
1931
|
+
[screenshot, screenshotOptions]
|
|
1932
|
+
);
|
|
1933
|
+
const getConsoleLogsFn = useCallback(
|
|
1934
|
+
(opts) => {
|
|
1935
|
+
if (!consoleCapture) {
|
|
1936
|
+
return { logs: [], totalCaptured: 0 };
|
|
1937
|
+
}
|
|
1938
|
+
return getConsoleLogs({ ...consoleOptions, ...opts });
|
|
1939
|
+
},
|
|
1940
|
+
[consoleCapture, consoleOptions]
|
|
1941
|
+
);
|
|
1942
|
+
const getNetworkRequestsFn = useCallback(
|
|
1943
|
+
(opts) => {
|
|
1944
|
+
if (!network) {
|
|
1945
|
+
return { requests: [], totalCaptured: 0 };
|
|
1946
|
+
}
|
|
1947
|
+
return getNetworkRequests({ ...networkOptions, ...opts });
|
|
1948
|
+
},
|
|
1949
|
+
[network, networkOptions]
|
|
1950
|
+
);
|
|
1951
|
+
const requestConsent = useCallback(
|
|
1952
|
+
async (tools, reason = "") => {
|
|
1953
|
+
const enabledTools = tools.filter((tool) => {
|
|
1954
|
+
if (tool === "screenshot") return screenshot;
|
|
1955
|
+
if (tool === "console") return consoleCapture;
|
|
1956
|
+
if (tool === "network") return network;
|
|
1957
|
+
return false;
|
|
1958
|
+
});
|
|
1959
|
+
if (enabledTools.length === 0) {
|
|
1960
|
+
return { approved: [], denied: [] };
|
|
1961
|
+
}
|
|
1962
|
+
if (!requireConsent) {
|
|
1963
|
+
return { approved: enabledTools, denied: [] };
|
|
1964
|
+
}
|
|
1965
|
+
const needsConsent = enabledTools.filter(
|
|
1966
|
+
(tool) => !rememberedConsentRef.current.has(tool)
|
|
1967
|
+
);
|
|
1968
|
+
if (needsConsent.length === 0) {
|
|
1969
|
+
return { approved: enabledTools, denied: [] };
|
|
1970
|
+
}
|
|
1971
|
+
const request = {
|
|
1972
|
+
tools: needsConsent,
|
|
1973
|
+
reason,
|
|
1974
|
+
keywords: []
|
|
1975
|
+
};
|
|
1976
|
+
if (onConsentRequest) {
|
|
1977
|
+
const response = await onConsentRequest(request);
|
|
1978
|
+
if (response.remember) {
|
|
1979
|
+
response.approved.forEach(
|
|
1980
|
+
(tool) => rememberedConsentRef.current.add(tool)
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
return response;
|
|
1984
|
+
}
|
|
1985
|
+
return new Promise((resolve) => {
|
|
1986
|
+
setPendingConsent(request);
|
|
1987
|
+
consentResolverRef.current = (response) => {
|
|
1988
|
+
if (response.remember) {
|
|
1989
|
+
response.approved.forEach(
|
|
1990
|
+
(tool) => rememberedConsentRef.current.add(tool)
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
resolve(response);
|
|
1994
|
+
};
|
|
1995
|
+
});
|
|
1996
|
+
},
|
|
1997
|
+
[screenshot, consoleCapture, network, requireConsent, onConsentRequest]
|
|
1998
|
+
);
|
|
1999
|
+
const respondToConsent = useCallback((response) => {
|
|
2000
|
+
if (consentResolverRef.current) {
|
|
2001
|
+
consentResolverRef.current(response);
|
|
2002
|
+
consentResolverRef.current = null;
|
|
2003
|
+
}
|
|
2004
|
+
setPendingConsent(null);
|
|
2005
|
+
}, []);
|
|
2006
|
+
const captureContext = useCallback(
|
|
2007
|
+
async (tools) => {
|
|
2008
|
+
const toolsToCapture = tools || ["screenshot", "console", "network"];
|
|
2009
|
+
const context = {
|
|
2010
|
+
timestamp: Date.now()
|
|
2011
|
+
};
|
|
2012
|
+
const captures = [];
|
|
2013
|
+
if (toolsToCapture.includes("screenshot") && screenshot) {
|
|
2014
|
+
captures.push(
|
|
2015
|
+
captureScreenshotFn().then((result) => {
|
|
2016
|
+
context.screenshot = result;
|
|
2017
|
+
}).catch(() => {
|
|
2018
|
+
})
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
if (toolsToCapture.includes("console") && consoleCapture) {
|
|
2022
|
+
context.consoleLogs = getConsoleLogsFn();
|
|
2023
|
+
}
|
|
2024
|
+
if (toolsToCapture.includes("network") && network) {
|
|
2025
|
+
context.networkRequests = getNetworkRequestsFn();
|
|
2026
|
+
}
|
|
2027
|
+
await Promise.all(captures);
|
|
2028
|
+
return context;
|
|
2029
|
+
},
|
|
2030
|
+
[
|
|
2031
|
+
screenshot,
|
|
2032
|
+
consoleCapture,
|
|
2033
|
+
network,
|
|
2034
|
+
captureScreenshotFn,
|
|
2035
|
+
getConsoleLogsFn,
|
|
2036
|
+
getNetworkRequestsFn
|
|
2037
|
+
]
|
|
2038
|
+
);
|
|
2039
|
+
const startCapturing = useCallback(() => {
|
|
2040
|
+
if (consoleCapture && !isConsoleCaptureActive()) {
|
|
2041
|
+
startConsoleCapture(consoleOptions);
|
|
2042
|
+
setActiveCaptures((prev) => ({ ...prev, console: true }));
|
|
2043
|
+
}
|
|
2044
|
+
if (network && !isNetworkCaptureActive()) {
|
|
2045
|
+
startNetworkCapture(networkOptions);
|
|
2046
|
+
setActiveCaptures((prev) => ({ ...prev, network: true }));
|
|
2047
|
+
}
|
|
2048
|
+
}, [consoleCapture, network, consoleOptions, networkOptions]);
|
|
2049
|
+
const stopCapturing = useCallback(() => {
|
|
2050
|
+
stopConsoleCapture();
|
|
2051
|
+
stopNetworkCapture();
|
|
2052
|
+
setActiveCaptures({ console: false, network: false });
|
|
2053
|
+
}, []);
|
|
2054
|
+
const clearCaptured = useCallback(() => {
|
|
2055
|
+
clearConsoleLogs();
|
|
2056
|
+
clearNetworkRequests();
|
|
2057
|
+
}, []);
|
|
2058
|
+
const formatForAI = useCallback((context) => {
|
|
2059
|
+
const parts = [];
|
|
2060
|
+
if (context.screenshot) {
|
|
2061
|
+
parts.push(
|
|
2062
|
+
`Screenshot captured (${context.screenshot.width}x${context.screenshot.height}, ${context.screenshot.format})`
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
if (context.consoleLogs && context.consoleLogs.logs.length > 0) {
|
|
2066
|
+
parts.push(formatLogsForAI(context.consoleLogs.logs));
|
|
2067
|
+
}
|
|
2068
|
+
if (context.networkRequests && context.networkRequests.requests.length > 0) {
|
|
2069
|
+
parts.push(formatRequestsForAI(context.networkRequests.requests));
|
|
2070
|
+
}
|
|
2071
|
+
return parts.length > 0 ? parts.join("\n\n---\n\n") : "No context captured.";
|
|
2072
|
+
}, []);
|
|
2073
|
+
const detectIntentFn = useCallback(
|
|
2074
|
+
(message) => {
|
|
2075
|
+
const result = detectIntent(message);
|
|
2076
|
+
result.suggestedTools = result.suggestedTools.filter((tool) => {
|
|
2077
|
+
if (tool === "screenshot") return screenshot;
|
|
2078
|
+
if (tool === "console") return consoleCapture;
|
|
2079
|
+
if (tool === "network") return network;
|
|
2080
|
+
return false;
|
|
2081
|
+
});
|
|
2082
|
+
return result;
|
|
2083
|
+
},
|
|
2084
|
+
[screenshot, consoleCapture, network]
|
|
2085
|
+
);
|
|
2086
|
+
return useMemo(
|
|
2087
|
+
() => ({
|
|
2088
|
+
isEnabled,
|
|
2089
|
+
activeCaptures,
|
|
2090
|
+
captureScreenshot: captureScreenshotFn,
|
|
2091
|
+
getConsoleLogs: getConsoleLogsFn,
|
|
2092
|
+
getNetworkRequests: getNetworkRequestsFn,
|
|
2093
|
+
captureContext,
|
|
2094
|
+
detectIntent: detectIntentFn,
|
|
2095
|
+
requestConsent,
|
|
2096
|
+
startCapturing,
|
|
2097
|
+
stopCapturing,
|
|
2098
|
+
clearCaptured,
|
|
2099
|
+
formatForAI,
|
|
2100
|
+
pendingConsent,
|
|
2101
|
+
respondToConsent
|
|
2102
|
+
}),
|
|
2103
|
+
[
|
|
2104
|
+
isEnabled,
|
|
2105
|
+
activeCaptures,
|
|
2106
|
+
captureScreenshotFn,
|
|
2107
|
+
getConsoleLogsFn,
|
|
2108
|
+
getNetworkRequestsFn,
|
|
2109
|
+
captureContext,
|
|
2110
|
+
detectIntentFn,
|
|
2111
|
+
requestConsent,
|
|
2112
|
+
startCapturing,
|
|
2113
|
+
stopCapturing,
|
|
2114
|
+
clearCaptured,
|
|
2115
|
+
formatForAI,
|
|
2116
|
+
pendingConsent,
|
|
2117
|
+
respondToConsent
|
|
2118
|
+
]
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
function useTool(config, dependencies = []) {
|
|
2122
|
+
const { registerTool, unregisterTool } = useCopilot();
|
|
2123
|
+
const configRef = useRef(config);
|
|
2124
|
+
configRef.current = config;
|
|
2125
|
+
useEffect(() => {
|
|
2126
|
+
const tool = {
|
|
2127
|
+
name: config.name,
|
|
2128
|
+
description: config.description,
|
|
2129
|
+
location: "client",
|
|
2130
|
+
inputSchema: config.inputSchema,
|
|
2131
|
+
handler: async (params, context) => {
|
|
2132
|
+
return configRef.current.handler(params, context);
|
|
2133
|
+
},
|
|
2134
|
+
render: config.render,
|
|
2135
|
+
available: config.available ?? true,
|
|
2136
|
+
needsApproval: config.needsApproval,
|
|
2137
|
+
approvalMessage: config.approvalMessage
|
|
2138
|
+
};
|
|
2139
|
+
registerTool(tool);
|
|
2140
|
+
return () => {
|
|
2141
|
+
unregisterTool(config.name);
|
|
2142
|
+
};
|
|
2143
|
+
}, [config.name, ...dependencies]);
|
|
2144
|
+
}
|
|
2145
|
+
function useTools(tools) {
|
|
2146
|
+
const { registerTool, unregisterTool } = useCopilot();
|
|
2147
|
+
const registeredToolsRef = useRef([]);
|
|
2148
|
+
const toolsRef = useRef(tools);
|
|
2149
|
+
toolsRef.current = tools;
|
|
2150
|
+
const toolsKey = Object.keys(tools).sort().join(",");
|
|
2151
|
+
useEffect(() => {
|
|
2152
|
+
const currentTools = toolsRef.current;
|
|
2153
|
+
const toolNames = [];
|
|
2154
|
+
for (const [name, toolDef] of Object.entries(currentTools)) {
|
|
2155
|
+
const fullTool = {
|
|
2156
|
+
...toolDef,
|
|
2157
|
+
name
|
|
2158
|
+
// Use the key as the name
|
|
2159
|
+
};
|
|
2160
|
+
registerTool(fullTool);
|
|
2161
|
+
toolNames.push(name);
|
|
2162
|
+
}
|
|
2163
|
+
registeredToolsRef.current = toolNames;
|
|
2164
|
+
return () => {
|
|
2165
|
+
for (const name of registeredToolsRef.current) {
|
|
2166
|
+
unregisterTool(name);
|
|
2167
|
+
}
|
|
2168
|
+
registeredToolsRef.current = [];
|
|
2169
|
+
};
|
|
2170
|
+
}, [toolsKey]);
|
|
2171
|
+
}
|
|
2172
|
+
var CopilotContext2 = createContext(null);
|
|
2173
|
+
function useCopilotContext() {
|
|
2174
|
+
const context = useContext(CopilotContext2);
|
|
2175
|
+
if (!context) {
|
|
2176
|
+
throw new Error("useCopilotContext must be used within a CopilotProvider");
|
|
2177
|
+
}
|
|
2178
|
+
return context;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
// src/react/hooks/useToolWithSchema.ts
|
|
2182
|
+
function convertZodSchema(schema, _toolName) {
|
|
2183
|
+
try {
|
|
2184
|
+
const zodWithJsonSchema = z;
|
|
2185
|
+
if (typeof zodWithJsonSchema.toJSONSchema === "function") {
|
|
2186
|
+
const jsonSchema = zodWithJsonSchema.toJSONSchema(
|
|
2187
|
+
schema
|
|
2188
|
+
);
|
|
2189
|
+
if (jsonSchema.type === "object") {
|
|
2190
|
+
return {
|
|
2191
|
+
type: "object",
|
|
2192
|
+
properties: jsonSchema.properties || {},
|
|
2193
|
+
required: jsonSchema.required
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
} catch {
|
|
2198
|
+
}
|
|
2199
|
+
return zodObjectToInputSchema(schema);
|
|
2200
|
+
}
|
|
2201
|
+
function useToolWithSchema(config, dependencies = []) {
|
|
2202
|
+
const { registerTool, unregisterTool } = useCopilotContext();
|
|
2203
|
+
const configRef = useRef(config);
|
|
2204
|
+
configRef.current = config;
|
|
2205
|
+
const inputSchema = useMemo(() => {
|
|
2206
|
+
try {
|
|
2207
|
+
return convertZodSchema(config.schema, config.name);
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
console.warn(
|
|
2210
|
+
`[useToolWithSchema] Failed to convert Zod schema for tool "${config.name}"`,
|
|
2211
|
+
error
|
|
2212
|
+
);
|
|
2213
|
+
return {
|
|
2214
|
+
type: "object",
|
|
2215
|
+
properties: {}
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
}, [config.schema, config.name]);
|
|
2219
|
+
useEffect(() => {
|
|
2220
|
+
const tool = {
|
|
2221
|
+
name: config.name,
|
|
2222
|
+
description: config.description,
|
|
2223
|
+
location: "client",
|
|
2224
|
+
inputSchema,
|
|
2225
|
+
handler: async (params, context) => {
|
|
2226
|
+
return configRef.current.handler(params, context);
|
|
2227
|
+
},
|
|
2228
|
+
render: config.render,
|
|
2229
|
+
available: config.available ?? true
|
|
2230
|
+
};
|
|
2231
|
+
registerTool(tool);
|
|
2232
|
+
return () => {
|
|
2233
|
+
unregisterTool(config.name);
|
|
2234
|
+
};
|
|
2235
|
+
}, [config.name, inputSchema, ...dependencies]);
|
|
2236
|
+
}
|
|
2237
|
+
function useToolsWithSchema(tools, dependencies = []) {
|
|
2238
|
+
const { registerTool, unregisterTool } = useCopilotContext();
|
|
2239
|
+
const toolsRef = useRef(tools);
|
|
2240
|
+
toolsRef.current = tools;
|
|
2241
|
+
useEffect(() => {
|
|
2242
|
+
const toolNames = [];
|
|
2243
|
+
for (const config of tools) {
|
|
2244
|
+
let inputSchema;
|
|
2245
|
+
try {
|
|
2246
|
+
inputSchema = convertZodSchema(config.schema, config.name);
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
console.warn(
|
|
2249
|
+
`[useToolsWithSchema] Failed to convert Zod schema for tool "${config.name}"`,
|
|
2250
|
+
error
|
|
2251
|
+
);
|
|
2252
|
+
inputSchema = { type: "object", properties: {} };
|
|
2253
|
+
}
|
|
2254
|
+
const tool = {
|
|
2255
|
+
name: config.name,
|
|
2256
|
+
description: config.description,
|
|
2257
|
+
location: "client",
|
|
2258
|
+
inputSchema,
|
|
2259
|
+
handler: async (params, context) => {
|
|
2260
|
+
const currentConfig = toolsRef.current.find(
|
|
2261
|
+
(t) => t.name === config.name
|
|
2262
|
+
);
|
|
2263
|
+
if (currentConfig) {
|
|
2264
|
+
return currentConfig.handler(params, context);
|
|
2265
|
+
}
|
|
2266
|
+
return { success: false, error: "Tool handler not found" };
|
|
2267
|
+
},
|
|
2268
|
+
available: config.available ?? true
|
|
2269
|
+
};
|
|
2270
|
+
registerTool(tool);
|
|
2271
|
+
toolNames.push(config.name);
|
|
2272
|
+
}
|
|
2273
|
+
return () => {
|
|
2274
|
+
for (const name of toolNames) {
|
|
2275
|
+
unregisterTool(name);
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
}, [tools.map((t) => t.name).join(","), ...dependencies]);
|
|
2279
|
+
}
|
|
2280
|
+
function useToolExecutor() {
|
|
2281
|
+
const {
|
|
2282
|
+
registeredTools,
|
|
2283
|
+
config,
|
|
2284
|
+
chat,
|
|
2285
|
+
addToolExecution,
|
|
2286
|
+
updateToolExecution
|
|
2287
|
+
} = useCopilotContext();
|
|
2288
|
+
const toolsRef = useRef(registeredTools);
|
|
2289
|
+
toolsRef.current = registeredTools;
|
|
2290
|
+
const executeTool = useCallback(
|
|
2291
|
+
async (toolCall) => {
|
|
2292
|
+
const tool = toolsRef.current.find((t) => t.name === toolCall.name);
|
|
2293
|
+
if (!tool) {
|
|
2294
|
+
return {
|
|
2295
|
+
success: false,
|
|
2296
|
+
error: `Unknown tool: ${toolCall.name}`
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
if (!tool.handler) {
|
|
2300
|
+
return {
|
|
2301
|
+
success: false,
|
|
2302
|
+
error: `Tool "${toolCall.name}" has no handler`
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const execution = {
|
|
2306
|
+
id: toolCall.id,
|
|
2307
|
+
name: toolCall.name,
|
|
2308
|
+
args: toolCall.input,
|
|
2309
|
+
status: "executing",
|
|
2310
|
+
timestamp: Date.now(),
|
|
2311
|
+
approvalStatus: "none"
|
|
2312
|
+
};
|
|
2313
|
+
addToolExecution?.(execution);
|
|
2314
|
+
try {
|
|
2315
|
+
const startTime = Date.now();
|
|
2316
|
+
const result = await tool.handler(toolCall.input);
|
|
2317
|
+
const duration = Date.now() - startTime;
|
|
2318
|
+
updateToolExecution?.(toolCall.id, {
|
|
2319
|
+
status: result.success ? "completed" : "error",
|
|
2320
|
+
result,
|
|
2321
|
+
error: result.error,
|
|
2322
|
+
duration
|
|
2323
|
+
});
|
|
2324
|
+
return result;
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
const errorMessage = error instanceof Error ? error.message : "Tool execution failed";
|
|
2327
|
+
updateToolExecution?.(toolCall.id, {
|
|
2328
|
+
status: "error",
|
|
2329
|
+
error: errorMessage
|
|
2330
|
+
});
|
|
2331
|
+
return {
|
|
2332
|
+
success: false,
|
|
2333
|
+
error: errorMessage
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
},
|
|
2337
|
+
[addToolExecution, updateToolExecution]
|
|
2338
|
+
);
|
|
2339
|
+
const sendToolResult = useCallback(
|
|
2340
|
+
async (toolCallId, result) => {
|
|
2341
|
+
const runtimeUrl = config.runtimeUrl || config.cloud?.endpoint;
|
|
2342
|
+
if (!runtimeUrl) {
|
|
2343
|
+
console.warn(
|
|
2344
|
+
"[useToolExecutor] No runtime URL configured, cannot send tool result"
|
|
2345
|
+
);
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
const baseUrl = runtimeUrl.replace(/\/chat\/?$/, "");
|
|
2349
|
+
try {
|
|
2350
|
+
const response = await fetch(`${baseUrl}/tool-result`, {
|
|
2351
|
+
method: "POST",
|
|
2352
|
+
headers: {
|
|
2353
|
+
"Content-Type": "application/json"
|
|
2354
|
+
},
|
|
2355
|
+
body: JSON.stringify({
|
|
2356
|
+
threadId: chat.threadId || "default",
|
|
2357
|
+
toolCallId,
|
|
2358
|
+
result
|
|
2359
|
+
})
|
|
2360
|
+
});
|
|
2361
|
+
if (!response.ok) {
|
|
2362
|
+
console.error(
|
|
2363
|
+
"[useToolExecutor] Failed to send tool result:",
|
|
2364
|
+
await response.text()
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
} catch (error) {
|
|
2368
|
+
console.error("[useToolExecutor] Error sending tool result:", error);
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
[config.runtimeUrl, config.cloud?.endpoint, chat.threadId]
|
|
2372
|
+
);
|
|
2373
|
+
const getTool = useCallback((name) => {
|
|
2374
|
+
return toolsRef.current.find((t) => t.name === name);
|
|
2375
|
+
}, []);
|
|
2376
|
+
const hasTool = useCallback((name) => {
|
|
2377
|
+
return toolsRef.current.some((t) => t.name === name);
|
|
2378
|
+
}, []);
|
|
2379
|
+
return {
|
|
2380
|
+
executeTool,
|
|
2381
|
+
sendToolResult,
|
|
2382
|
+
getTool,
|
|
2383
|
+
hasTool
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
function useSuggestions(options = {}) {
|
|
2387
|
+
const {
|
|
2388
|
+
count = 3,
|
|
2389
|
+
context,
|
|
2390
|
+
suggestions: staticSuggestions,
|
|
2391
|
+
autoRefresh = true
|
|
2392
|
+
} = options;
|
|
2393
|
+
const { chat, actions, config } = useCopilotContext();
|
|
2394
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
2395
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
2396
|
+
const normalizedStatic = useMemo(
|
|
2397
|
+
() => staticSuggestions?.map((s) => typeof s === "string" ? { text: s } : s),
|
|
2398
|
+
[staticSuggestions]
|
|
2399
|
+
);
|
|
2400
|
+
const refresh = useCallback(async () => {
|
|
2401
|
+
if (normalizedStatic) {
|
|
2402
|
+
setSuggestions(normalizedStatic.slice(0, count));
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (!config.cloud) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
setIsLoading(true);
|
|
2409
|
+
try {
|
|
2410
|
+
const endpoint = config.cloud.endpoint || "https://api.yourgpt.ai/v1";
|
|
2411
|
+
const response = await fetch(`${endpoint}/suggestions`, {
|
|
2412
|
+
method: "POST",
|
|
2413
|
+
headers: {
|
|
2414
|
+
"Content-Type": "application/json",
|
|
2415
|
+
Authorization: `Bearer ${config.cloud.apiKey}`
|
|
2416
|
+
},
|
|
2417
|
+
body: JSON.stringify({
|
|
2418
|
+
botId: config.cloud.botId,
|
|
2419
|
+
count,
|
|
2420
|
+
context,
|
|
2421
|
+
messages: chat.messages.slice(-5)
|
|
2422
|
+
// Last 5 messages for context
|
|
2423
|
+
})
|
|
2424
|
+
});
|
|
2425
|
+
if (response.ok) {
|
|
2426
|
+
const data = await response.json();
|
|
2427
|
+
setSuggestions(
|
|
2428
|
+
data.suggestions.map(
|
|
2429
|
+
(s) => typeof s === "string" ? { text: s } : s
|
|
2430
|
+
)
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
console.error("Failed to fetch suggestions:", error);
|
|
2435
|
+
} finally {
|
|
2436
|
+
setIsLoading(false);
|
|
2437
|
+
}
|
|
2438
|
+
}, [config.cloud, count, context, chat.messages, normalizedStatic]);
|
|
2439
|
+
const select = useCallback(
|
|
2440
|
+
(suggestion) => {
|
|
2441
|
+
const text = typeof suggestion === "string" ? suggestion : suggestion.text;
|
|
2442
|
+
actions.sendMessage(text);
|
|
2443
|
+
},
|
|
2444
|
+
[actions]
|
|
2445
|
+
);
|
|
2446
|
+
useEffect(() => {
|
|
2447
|
+
if (autoRefresh && chat.messages.length === 0) {
|
|
2448
|
+
refresh();
|
|
2449
|
+
}
|
|
2450
|
+
}, [autoRefresh, chat.messages.length, refresh]);
|
|
2451
|
+
return {
|
|
2452
|
+
suggestions: normalizedStatic?.slice(0, count) || suggestions,
|
|
2453
|
+
isLoading,
|
|
2454
|
+
refresh,
|
|
2455
|
+
select
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function useAgent(options) {
|
|
2459
|
+
const { name, initialState = {}, onStateChange } = options;
|
|
2460
|
+
const { config } = useCopilotContext();
|
|
2461
|
+
const [state, setStateInternal] = useState(initialState);
|
|
2462
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
2463
|
+
const [nodeName, setNodeName] = useState(null);
|
|
2464
|
+
const [error, setError] = useState(null);
|
|
2465
|
+
const abortControllerRef = useRef(null);
|
|
2466
|
+
const getEndpoint = useCallback(() => {
|
|
2467
|
+
if (config.cloud) {
|
|
2468
|
+
return `${config.cloud.endpoint || "https://api.yourgpt.ai/v1"}/agents/${name}`;
|
|
2469
|
+
}
|
|
2470
|
+
return `${config.runtimeUrl || "/api"}/agents/${name}`;
|
|
2471
|
+
}, [config, name]);
|
|
2472
|
+
const start = useCallback(
|
|
2473
|
+
async (input) => {
|
|
2474
|
+
setIsRunning(true);
|
|
2475
|
+
setError(null);
|
|
2476
|
+
abortControllerRef.current = new AbortController();
|
|
2477
|
+
try {
|
|
2478
|
+
const endpoint = getEndpoint();
|
|
2479
|
+
const headers = {
|
|
2480
|
+
"Content-Type": "application/json"
|
|
2481
|
+
};
|
|
2482
|
+
if (config.cloud?.apiKey) {
|
|
2483
|
+
headers["Authorization"] = `Bearer ${config.cloud.apiKey}`;
|
|
2484
|
+
}
|
|
2485
|
+
const response = await fetch(`${endpoint}/start`, {
|
|
2486
|
+
method: "POST",
|
|
2487
|
+
headers,
|
|
2488
|
+
body: JSON.stringify({
|
|
2489
|
+
input: typeof input === "string" ? { message: input } : input,
|
|
2490
|
+
state
|
|
2491
|
+
}),
|
|
2492
|
+
signal: abortControllerRef.current.signal
|
|
2493
|
+
});
|
|
2494
|
+
if (!response.ok) {
|
|
2495
|
+
throw new Error(`Agent error: ${response.status}`);
|
|
2496
|
+
}
|
|
2497
|
+
for await (const event of streamSSE(response)) {
|
|
2498
|
+
handleAgentEvent(event);
|
|
2499
|
+
}
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
if (err.name !== "AbortError") {
|
|
2502
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
2503
|
+
}
|
|
2504
|
+
} finally {
|
|
2505
|
+
setIsRunning(false);
|
|
2506
|
+
abortControllerRef.current = null;
|
|
2507
|
+
}
|
|
2508
|
+
},
|
|
2509
|
+
[config, getEndpoint, state]
|
|
2510
|
+
);
|
|
2511
|
+
const handleAgentEvent = useCallback(
|
|
2512
|
+
(event) => {
|
|
2513
|
+
if (event.type === "error") {
|
|
2514
|
+
setError(new Error(event.message));
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
if ("state" in event && event.state) {
|
|
2518
|
+
setStateInternal(event.state);
|
|
2519
|
+
onStateChange?.(event.state);
|
|
2520
|
+
}
|
|
2521
|
+
if ("nodeName" in event && event.nodeName) {
|
|
2522
|
+
setNodeName(event.nodeName);
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2525
|
+
[onStateChange]
|
|
2526
|
+
);
|
|
2527
|
+
const stop = useCallback(() => {
|
|
2528
|
+
abortControllerRef.current?.abort();
|
|
2529
|
+
}, []);
|
|
2530
|
+
const setState = useCallback(
|
|
2531
|
+
(partialState) => {
|
|
2532
|
+
setStateInternal((prev) => {
|
|
2533
|
+
const newState = { ...prev, ...partialState };
|
|
2534
|
+
onStateChange?.(newState);
|
|
2535
|
+
return newState;
|
|
2536
|
+
});
|
|
2537
|
+
},
|
|
2538
|
+
[onStateChange]
|
|
2539
|
+
);
|
|
2540
|
+
useEffect(() => {
|
|
2541
|
+
return () => {
|
|
2542
|
+
abortControllerRef.current?.abort();
|
|
2543
|
+
};
|
|
2544
|
+
}, []);
|
|
2545
|
+
return {
|
|
2546
|
+
state,
|
|
2547
|
+
isRunning,
|
|
2548
|
+
nodeName,
|
|
2549
|
+
start,
|
|
2550
|
+
stop,
|
|
2551
|
+
setState,
|
|
2552
|
+
error
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// src/react/utils/knowledge-base.ts
|
|
2557
|
+
var KNOWLEDGE_BASE_API = "https://api.yourgpt.ai/chatbot/v1/searchIndexDocument";
|
|
2558
|
+
async function searchKnowledgeBase(query, config) {
|
|
2559
|
+
try {
|
|
2560
|
+
const response = await fetch(KNOWLEDGE_BASE_API, {
|
|
2561
|
+
method: "POST",
|
|
2562
|
+
headers: {
|
|
2563
|
+
accept: "*/*",
|
|
2564
|
+
"content-type": "application/json",
|
|
2565
|
+
authorization: `Bearer ${config.token}`
|
|
2566
|
+
},
|
|
2567
|
+
body: JSON.stringify({
|
|
2568
|
+
project_uid: config.projectUid,
|
|
2569
|
+
query,
|
|
2570
|
+
page: 1,
|
|
2571
|
+
limit: String(config.limit || 10),
|
|
2572
|
+
app_id: config.appId || "1"
|
|
2573
|
+
})
|
|
2574
|
+
});
|
|
2575
|
+
if (!response.ok) {
|
|
2576
|
+
return {
|
|
2577
|
+
success: false,
|
|
2578
|
+
results: [],
|
|
2579
|
+
error: `API error: ${response.status} ${response.statusText}`
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
const data = await response.json();
|
|
2583
|
+
const results = (data.data || data.results || []).map((item) => ({
|
|
2584
|
+
id: item.id || item._id || String(Math.random()),
|
|
2585
|
+
title: item.title || item.name || void 0,
|
|
2586
|
+
content: item.content || item.text || item.snippet || "",
|
|
2587
|
+
score: item.score || item.relevance || void 0,
|
|
2588
|
+
url: item.url || item.source_url || void 0,
|
|
2589
|
+
metadata: item.metadata || {}
|
|
2590
|
+
}));
|
|
2591
|
+
return {
|
|
2592
|
+
success: true,
|
|
2593
|
+
results,
|
|
2594
|
+
total: data.total || results.length,
|
|
2595
|
+
page: data.page || 1
|
|
2596
|
+
};
|
|
2597
|
+
} catch (error) {
|
|
2598
|
+
return {
|
|
2599
|
+
success: false,
|
|
2600
|
+
results: [],
|
|
2601
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
function formatKnowledgeResultsForAI(results) {
|
|
2606
|
+
if (results.length === 0) {
|
|
2607
|
+
return "No relevant documents found in the knowledge base.";
|
|
2608
|
+
}
|
|
2609
|
+
return results.map((result, index) => {
|
|
2610
|
+
const parts = [`[${index + 1}]`];
|
|
2611
|
+
if (result.title) parts.push(`**${result.title}**`);
|
|
2612
|
+
parts.push(result.content);
|
|
2613
|
+
if (result.url) parts.push(`Source: ${result.url}`);
|
|
2614
|
+
return parts.join("\n");
|
|
2615
|
+
}).join("\n\n---\n\n");
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/react/hooks/useKnowledgeBase.ts
|
|
2619
|
+
function useKnowledgeBase(config) {
|
|
2620
|
+
const { registerTool, unregisterTool } = useCopilotContext();
|
|
2621
|
+
const configRef = useRef(config);
|
|
2622
|
+
configRef.current = config;
|
|
2623
|
+
const handleSearch = useCallback(
|
|
2624
|
+
async (params) => {
|
|
2625
|
+
const query = params.query;
|
|
2626
|
+
if (!query) {
|
|
2627
|
+
return {
|
|
2628
|
+
success: false,
|
|
2629
|
+
error: "Query is required"
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
const currentConfig = configRef.current;
|
|
2633
|
+
const kbConfig = {
|
|
2634
|
+
projectUid: currentConfig.projectUid,
|
|
2635
|
+
token: currentConfig.token,
|
|
2636
|
+
appId: currentConfig.appId,
|
|
2637
|
+
limit: currentConfig.limit || 5
|
|
2638
|
+
};
|
|
2639
|
+
const response = await searchKnowledgeBase(
|
|
2640
|
+
query,
|
|
2641
|
+
kbConfig
|
|
2642
|
+
);
|
|
2643
|
+
if (!response.success) {
|
|
2644
|
+
return {
|
|
2645
|
+
success: false,
|
|
2646
|
+
error: response.error || "Knowledge base search failed"
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
const formattedResults = formatKnowledgeResultsForAI(response.results);
|
|
2650
|
+
return {
|
|
2651
|
+
success: true,
|
|
2652
|
+
message: formattedResults,
|
|
2653
|
+
data: {
|
|
2654
|
+
resultCount: response.results.length,
|
|
2655
|
+
total: response.total
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
},
|
|
2659
|
+
[]
|
|
2660
|
+
);
|
|
2661
|
+
useEffect(() => {
|
|
2662
|
+
if (config.enabled === false) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
registerTool({
|
|
2666
|
+
name: "search_knowledge",
|
|
2667
|
+
description: "Search the knowledge base for relevant information about the product, documentation, or company. Use this to answer questions about features, pricing, policies, guides, or any factual information.",
|
|
2668
|
+
location: "client",
|
|
2669
|
+
inputSchema: {
|
|
2670
|
+
type: "object",
|
|
2671
|
+
properties: {
|
|
2672
|
+
query: {
|
|
2673
|
+
type: "string",
|
|
2674
|
+
description: "The search query to find relevant information in the knowledge base"
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
required: ["query"]
|
|
2678
|
+
},
|
|
2679
|
+
handler: handleSearch
|
|
2680
|
+
});
|
|
2681
|
+
return () => {
|
|
2682
|
+
unregisterTool("search_knowledge");
|
|
2683
|
+
};
|
|
2684
|
+
}, [
|
|
2685
|
+
config.enabled,
|
|
2686
|
+
config.projectUid,
|
|
2687
|
+
config.token,
|
|
2688
|
+
registerTool,
|
|
2689
|
+
unregisterTool,
|
|
2690
|
+
handleSearch
|
|
2691
|
+
]);
|
|
2692
|
+
}
|
|
2693
|
+
var DEFAULT_CAPABILITIES = {
|
|
2694
|
+
supportsVision: false,
|
|
2695
|
+
supportsTools: true,
|
|
2696
|
+
supportsThinking: false,
|
|
2697
|
+
supportsStreaming: true,
|
|
2698
|
+
supportsPDF: false,
|
|
2699
|
+
supportsAudio: false,
|
|
2700
|
+
supportsVideo: false,
|
|
2701
|
+
maxTokens: 8192,
|
|
2702
|
+
supportedImageTypes: [],
|
|
2703
|
+
supportsJsonMode: false,
|
|
2704
|
+
supportsSystemMessages: true
|
|
2705
|
+
};
|
|
2706
|
+
function useCapabilities() {
|
|
2707
|
+
const { config } = useCopilotContext();
|
|
2708
|
+
const [capabilities, setCapabilities] = useState(DEFAULT_CAPABILITIES);
|
|
2709
|
+
const [provider, setProvider] = useState("unknown");
|
|
2710
|
+
const [model, setModel] = useState("unknown");
|
|
2711
|
+
const [supportedModels, setSupportedModels] = useState([]);
|
|
2712
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2713
|
+
const [error, setError] = useState(null);
|
|
2714
|
+
const capabilitiesUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/chat\/?$/, "/capabilities") : null;
|
|
2715
|
+
const fetchCapabilities = useCallback(async () => {
|
|
2716
|
+
if (!capabilitiesUrl) {
|
|
2717
|
+
setIsLoading(false);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
try {
|
|
2721
|
+
setIsLoading(true);
|
|
2722
|
+
setError(null);
|
|
2723
|
+
const response = await fetch(capabilitiesUrl);
|
|
2724
|
+
if (!response.ok) {
|
|
2725
|
+
throw new Error(`Failed to fetch capabilities: ${response.status}`);
|
|
2726
|
+
}
|
|
2727
|
+
const data = await response.json();
|
|
2728
|
+
setCapabilities(data.capabilities);
|
|
2729
|
+
setProvider(data.provider);
|
|
2730
|
+
setModel(data.model);
|
|
2731
|
+
setSupportedModels(data.supportedModels);
|
|
2732
|
+
} catch (err) {
|
|
2733
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
2734
|
+
} finally {
|
|
2735
|
+
setIsLoading(false);
|
|
2736
|
+
}
|
|
2737
|
+
}, [capabilitiesUrl]);
|
|
2738
|
+
useEffect(() => {
|
|
2739
|
+
fetchCapabilities();
|
|
2740
|
+
}, [fetchCapabilities]);
|
|
2741
|
+
return {
|
|
2742
|
+
/** Current model capabilities */
|
|
2743
|
+
capabilities,
|
|
2744
|
+
/** Current provider name */
|
|
2745
|
+
provider,
|
|
2746
|
+
/** Current model ID */
|
|
2747
|
+
model,
|
|
2748
|
+
/** List of supported models for current provider */
|
|
2749
|
+
supportedModels,
|
|
2750
|
+
/** Whether capabilities are being loaded */
|
|
2751
|
+
isLoading,
|
|
2752
|
+
/** Error if fetch failed */
|
|
2753
|
+
error,
|
|
2754
|
+
/** Refetch capabilities */
|
|
2755
|
+
refetch: fetchCapabilities
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
function useFeatureSupport(feature) {
|
|
2759
|
+
const { capabilities } = useCapabilities();
|
|
2760
|
+
return capabilities[feature] ?? false;
|
|
2761
|
+
}
|
|
2762
|
+
function useSupportedMediaTypes() {
|
|
2763
|
+
const { capabilities } = useCapabilities();
|
|
2764
|
+
return {
|
|
2765
|
+
/** Supported image MIME types */
|
|
2766
|
+
imageTypes: capabilities.supportedImageTypes || [],
|
|
2767
|
+
/** Supported audio MIME types */
|
|
2768
|
+
audioTypes: capabilities.supportedAudioTypes || [],
|
|
2769
|
+
/** Supported video MIME types */
|
|
2770
|
+
videoTypes: capabilities.supportedVideoTypes || [],
|
|
2771
|
+
/** Whether any image types are supported */
|
|
2772
|
+
hasImageSupport: (capabilities.supportedImageTypes?.length ?? 0) > 0,
|
|
2773
|
+
/** Whether any audio types are supported */
|
|
2774
|
+
hasAudioSupport: (capabilities.supportedAudioTypes?.length ?? 0) > 0,
|
|
2775
|
+
/** Whether any video types are supported */
|
|
2776
|
+
hasVideoSupport: (capabilities.supportedVideoTypes?.length ?? 0) > 0
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
function useDevLogger() {
|
|
2780
|
+
const ctx = useCopilotContext();
|
|
2781
|
+
return useMemo(() => {
|
|
2782
|
+
const toolExecutions = (ctx.agentLoop?.toolExecutions || []).map(
|
|
2783
|
+
(exec) => ({
|
|
2784
|
+
id: exec.id,
|
|
2785
|
+
name: exec.name,
|
|
2786
|
+
status: exec.status,
|
|
2787
|
+
approvalStatus: exec.approvalStatus || "not_required"
|
|
2788
|
+
})
|
|
2789
|
+
);
|
|
2790
|
+
const pendingApprovalsCount = ctx.pendingApprovals?.length || 0;
|
|
2791
|
+
const registeredTools = (ctx.registeredTools || []).map((tool) => ({
|
|
2792
|
+
name: tool.name,
|
|
2793
|
+
location: tool.location || "client"
|
|
2794
|
+
}));
|
|
2795
|
+
const registeredActions = (ctx.registeredActions || []).map((action) => ({
|
|
2796
|
+
name: action.name
|
|
2797
|
+
}));
|
|
2798
|
+
const storedPermissions = (ctx.storedPermissions || []).map((p) => ({
|
|
2799
|
+
toolName: p.toolName,
|
|
2800
|
+
level: p.level
|
|
2801
|
+
}));
|
|
2802
|
+
return {
|
|
2803
|
+
chat: {
|
|
2804
|
+
isLoading: ctx.chat?.isLoading || false,
|
|
2805
|
+
messageCount: ctx.chat?.messages?.length || 0,
|
|
2806
|
+
threadId: ctx.chat?.threadId || "none",
|
|
2807
|
+
error: ctx.chat?.error?.message || null
|
|
2808
|
+
},
|
|
2809
|
+
tools: {
|
|
2810
|
+
isEnabled: !!ctx.toolsConfig,
|
|
2811
|
+
isCapturing: ctx.tools?.isCapturing || false,
|
|
2812
|
+
pendingConsent: !!ctx.tools?.pendingConsent
|
|
2813
|
+
},
|
|
2814
|
+
agentLoop: {
|
|
2815
|
+
toolExecutions,
|
|
2816
|
+
pendingApprovals: pendingApprovalsCount,
|
|
2817
|
+
iteration: ctx.agentLoop?.iteration || 0,
|
|
2818
|
+
maxIterations: ctx.agentLoop?.maxIterations || 10
|
|
2819
|
+
},
|
|
2820
|
+
registered: {
|
|
2821
|
+
tools: registeredTools,
|
|
2822
|
+
actions: registeredActions,
|
|
2823
|
+
contextCount: ctx.contextTree?.length || 0
|
|
2824
|
+
},
|
|
2825
|
+
permissions: {
|
|
2826
|
+
stored: storedPermissions,
|
|
2827
|
+
loaded: ctx.permissionsLoaded || false
|
|
2828
|
+
},
|
|
2829
|
+
config: {
|
|
2830
|
+
provider: ctx.config?.config?.provider || (ctx.config?.cloud ? "yourgpt-cloud" : "unknown"),
|
|
2831
|
+
model: ctx.config?.config?.model || "default",
|
|
2832
|
+
runtimeUrl: ctx.config?.runtimeUrl || ctx.config?.cloud?.endpoint || ""
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
}, [
|
|
2836
|
+
ctx.chat,
|
|
2837
|
+
ctx.tools,
|
|
2838
|
+
ctx.toolsConfig,
|
|
2839
|
+
ctx.agentLoop,
|
|
2840
|
+
ctx.pendingApprovals,
|
|
2841
|
+
ctx.registeredTools,
|
|
2842
|
+
ctx.registeredActions,
|
|
2843
|
+
ctx.contextTree,
|
|
2844
|
+
ctx.storedPermissions,
|
|
2845
|
+
ctx.permissionsLoaded,
|
|
2846
|
+
ctx.config
|
|
2847
|
+
]);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/react/utils/permission-storage.ts
|
|
2851
|
+
var DEFAULT_KEY_PREFIX = "yourgpt-permissions";
|
|
2852
|
+
function createPermissionStorage(config) {
|
|
2853
|
+
switch (config.type) {
|
|
2854
|
+
case "localStorage":
|
|
2855
|
+
return createBrowserStorageAdapter(
|
|
2856
|
+
typeof window !== "undefined" ? localStorage : null,
|
|
2857
|
+
config.keyPrefix
|
|
2858
|
+
);
|
|
2859
|
+
case "sessionStorage":
|
|
2860
|
+
return createBrowserStorageAdapter(
|
|
2861
|
+
typeof window !== "undefined" ? sessionStorage : null,
|
|
2862
|
+
config.keyPrefix
|
|
2863
|
+
);
|
|
2864
|
+
case "memory":
|
|
2865
|
+
default:
|
|
2866
|
+
return createMemoryStorageAdapter();
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
function createBrowserStorageAdapter(storage, keyPrefix = DEFAULT_KEY_PREFIX) {
|
|
2870
|
+
const getStorageKey = () => keyPrefix;
|
|
2871
|
+
const loadPermissions = () => {
|
|
2872
|
+
if (!storage) return /* @__PURE__ */ new Map();
|
|
2873
|
+
try {
|
|
2874
|
+
const data = storage.getItem(getStorageKey());
|
|
2875
|
+
if (!data) return /* @__PURE__ */ new Map();
|
|
2876
|
+
const parsed = JSON.parse(data);
|
|
2877
|
+
return new Map(parsed.map((p) => [p.toolName, p]));
|
|
2878
|
+
} catch {
|
|
2879
|
+
return /* @__PURE__ */ new Map();
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
const savePermissions = (permissions) => {
|
|
2883
|
+
if (!storage) return;
|
|
2884
|
+
try {
|
|
2885
|
+
storage.setItem(
|
|
2886
|
+
getStorageKey(),
|
|
2887
|
+
JSON.stringify(Array.from(permissions.values()))
|
|
2888
|
+
);
|
|
2889
|
+
} catch (e) {
|
|
2890
|
+
console.warn("[PermissionStorage] Failed to save permissions:", e);
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
return {
|
|
2894
|
+
async get(toolName) {
|
|
2895
|
+
const permissions = loadPermissions();
|
|
2896
|
+
return permissions.get(toolName) || null;
|
|
2897
|
+
},
|
|
2898
|
+
async set(permission) {
|
|
2899
|
+
const permissions = loadPermissions();
|
|
2900
|
+
permissions.set(permission.toolName, permission);
|
|
2901
|
+
savePermissions(permissions);
|
|
2902
|
+
},
|
|
2903
|
+
async remove(toolName) {
|
|
2904
|
+
const permissions = loadPermissions();
|
|
2905
|
+
permissions.delete(toolName);
|
|
2906
|
+
savePermissions(permissions);
|
|
2907
|
+
},
|
|
2908
|
+
async getAll() {
|
|
2909
|
+
const permissions = loadPermissions();
|
|
2910
|
+
return Array.from(permissions.values());
|
|
2911
|
+
},
|
|
2912
|
+
async clear() {
|
|
2913
|
+
if (!storage) return;
|
|
2914
|
+
storage.removeItem(getStorageKey());
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function createMemoryStorageAdapter() {
|
|
2919
|
+
const permissions = /* @__PURE__ */ new Map();
|
|
2920
|
+
return {
|
|
2921
|
+
async get(toolName) {
|
|
2922
|
+
return permissions.get(toolName) || null;
|
|
2923
|
+
},
|
|
2924
|
+
async set(permission) {
|
|
2925
|
+
permissions.set(permission.toolName, permission);
|
|
2926
|
+
},
|
|
2927
|
+
async remove(toolName) {
|
|
2928
|
+
permissions.delete(toolName);
|
|
2929
|
+
},
|
|
2930
|
+
async getAll() {
|
|
2931
|
+
return Array.from(permissions.values());
|
|
2932
|
+
},
|
|
2933
|
+
async clear() {
|
|
2934
|
+
permissions.clear();
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
function createSessionPermissionCache() {
|
|
2939
|
+
return /* @__PURE__ */ new Map();
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/react/internal/ReactChat.ts
|
|
2943
|
+
var ReactChat = class extends AbstractChat {
|
|
2944
|
+
constructor(config) {
|
|
2945
|
+
const reactState = new ReactChatState(config.initialMessages);
|
|
2946
|
+
const init = {
|
|
2947
|
+
runtimeUrl: config.runtimeUrl,
|
|
2948
|
+
systemPrompt: config.systemPrompt,
|
|
2949
|
+
llm: config.llm,
|
|
2950
|
+
threadId: config.threadId,
|
|
2951
|
+
streaming: config.streaming ?? true,
|
|
2952
|
+
headers: config.headers,
|
|
2953
|
+
initialMessages: config.initialMessages,
|
|
2954
|
+
state: reactState,
|
|
2955
|
+
callbacks: config.callbacks,
|
|
2956
|
+
debug: config.debug
|
|
2957
|
+
};
|
|
2958
|
+
super(init);
|
|
2959
|
+
// ============================================
|
|
2960
|
+
// Subscribe (for useSyncExternalStore)
|
|
2961
|
+
// ============================================
|
|
2962
|
+
/**
|
|
2963
|
+
* Subscribe to state changes.
|
|
2964
|
+
* Returns an unsubscribe function.
|
|
2965
|
+
*
|
|
2966
|
+
* @example
|
|
2967
|
+
* ```tsx
|
|
2968
|
+
* const messages = useSyncExternalStore(
|
|
2969
|
+
* chat.subscribe,
|
|
2970
|
+
* () => chat.messages
|
|
2971
|
+
* );
|
|
2972
|
+
* ```
|
|
2973
|
+
*/
|
|
2974
|
+
this.subscribe = (callback) => {
|
|
2975
|
+
return this.reactState.subscribe(callback);
|
|
2976
|
+
};
|
|
2977
|
+
this.reactState = reactState;
|
|
2978
|
+
}
|
|
2979
|
+
// ============================================
|
|
2980
|
+
// Event handling shortcuts
|
|
2981
|
+
// ============================================
|
|
2982
|
+
/**
|
|
2983
|
+
* Subscribe to tool calls events
|
|
2984
|
+
*/
|
|
2985
|
+
onToolCalls(handler) {
|
|
2986
|
+
return this.on("toolCalls", handler);
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Subscribe to done events
|
|
2990
|
+
*/
|
|
2991
|
+
onDone(handler) {
|
|
2992
|
+
return this.on("done", handler);
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2995
|
+
* Subscribe to error events
|
|
2996
|
+
*/
|
|
2997
|
+
onError(handler) {
|
|
2998
|
+
return this.on("error", handler);
|
|
2999
|
+
}
|
|
3000
|
+
// ============================================
|
|
3001
|
+
// Override dispose to clean up state
|
|
3002
|
+
// ============================================
|
|
3003
|
+
dispose() {
|
|
3004
|
+
super.dispose();
|
|
3005
|
+
this.reactState.dispose();
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
function createReactChat(config) {
|
|
3009
|
+
return new ReactChat(config);
|
|
3010
|
+
}
|
|
3011
|
+
function useChat(config) {
|
|
3012
|
+
const chatRef = useRef(null);
|
|
3013
|
+
const [input, setInput] = useState("");
|
|
3014
|
+
if (chatRef.current === null) {
|
|
3015
|
+
chatRef.current = createReactChat({
|
|
3016
|
+
runtimeUrl: config.runtimeUrl,
|
|
3017
|
+
systemPrompt: config.systemPrompt,
|
|
3018
|
+
llm: config.llm,
|
|
3019
|
+
threadId: config.threadId,
|
|
3020
|
+
streaming: config.streaming,
|
|
3021
|
+
headers: config.headers,
|
|
3022
|
+
initialMessages: config.initialMessages,
|
|
3023
|
+
debug: config.debug,
|
|
3024
|
+
callbacks: {
|
|
3025
|
+
onMessagesChange: config.onMessagesChange,
|
|
3026
|
+
onError: config.onError,
|
|
3027
|
+
onFinish: config.onFinish,
|
|
3028
|
+
onToolCalls: config.onToolCalls
|
|
3029
|
+
}
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
const messages = useSyncExternalStore(
|
|
3033
|
+
chatRef.current.subscribe,
|
|
3034
|
+
() => chatRef.current.messages,
|
|
3035
|
+
() => chatRef.current.messages
|
|
3036
|
+
// Server snapshot
|
|
3037
|
+
);
|
|
3038
|
+
const status = useSyncExternalStore(
|
|
3039
|
+
chatRef.current.subscribe,
|
|
3040
|
+
() => chatRef.current.status,
|
|
3041
|
+
() => "ready"
|
|
3042
|
+
// Server snapshot
|
|
3043
|
+
);
|
|
3044
|
+
const error = useSyncExternalStore(
|
|
3045
|
+
chatRef.current.subscribe,
|
|
3046
|
+
() => chatRef.current.error,
|
|
3047
|
+
() => void 0
|
|
3048
|
+
// Server snapshot
|
|
3049
|
+
);
|
|
3050
|
+
const isLoading = status === "streaming" || status === "submitted";
|
|
3051
|
+
const sendMessage = useCallback(
|
|
3052
|
+
async (content, attachments) => {
|
|
3053
|
+
await chatRef.current?.sendMessage(content, attachments);
|
|
3054
|
+
setInput("");
|
|
3055
|
+
},
|
|
3056
|
+
[]
|
|
3057
|
+
);
|
|
3058
|
+
const stop = useCallback(() => {
|
|
3059
|
+
chatRef.current?.stop();
|
|
3060
|
+
}, []);
|
|
3061
|
+
const clearMessages = useCallback(() => {
|
|
3062
|
+
chatRef.current?.clearMessages();
|
|
3063
|
+
}, []);
|
|
3064
|
+
const setMessages = useCallback((messages2) => {
|
|
3065
|
+
chatRef.current?.setMessages(messages2);
|
|
3066
|
+
}, []);
|
|
3067
|
+
const regenerate = useCallback(async (messageId) => {
|
|
3068
|
+
await chatRef.current?.regenerate(messageId);
|
|
3069
|
+
}, []);
|
|
3070
|
+
const continueWithToolResults = useCallback(
|
|
3071
|
+
async (toolResults) => {
|
|
3072
|
+
await chatRef.current?.continueWithToolResults(toolResults);
|
|
3073
|
+
},
|
|
3074
|
+
[]
|
|
3075
|
+
);
|
|
3076
|
+
useEffect(() => {
|
|
3077
|
+
return () => {
|
|
3078
|
+
chatRef.current?.dispose();
|
|
3079
|
+
};
|
|
3080
|
+
}, []);
|
|
3081
|
+
return {
|
|
3082
|
+
messages,
|
|
3083
|
+
status,
|
|
3084
|
+
error,
|
|
3085
|
+
isLoading,
|
|
3086
|
+
input,
|
|
3087
|
+
setInput,
|
|
3088
|
+
sendMessage,
|
|
3089
|
+
stop,
|
|
3090
|
+
clearMessages,
|
|
3091
|
+
setMessages,
|
|
3092
|
+
regenerate,
|
|
3093
|
+
continueWithToolResults,
|
|
3094
|
+
chatRef
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
export { AbstractAgentLoop, AbstractChat, CopilotProvider, ReactChat, ReactChatState, createPermissionStorage, createReactChat, createReactChatState, createSessionPermissionCache, formatKnowledgeResultsForAI, initialAgentLoopState, searchKnowledgeBase, useAIAction, useAIActions, useAIContext, useAIContexts, useAITools, useAgent, useCapabilities, useChat, useCopilot, useDevLogger, useFeatureSupport, useKnowledgeBase, useSuggestions, useSupportedMediaTypes, useTool, useToolExecutor, useToolWithSchema, useTools, useToolsWithSchema };
|
|
3099
|
+
//# sourceMappingURL=chunk-QUGTRQSS.js.map
|
|
3100
|
+
//# sourceMappingURL=chunk-QUGTRQSS.js.map
|