@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.
@@ -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