@yourgpt/copilot-sdk 2.5.1-beta.1 → 2.5.2-alpha.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.
@@ -1555,6 +1555,10 @@ var AbstractChat = class {
1555
1555
  get isStreaming() {
1556
1556
  return this.transport.isStreaming();
1557
1557
  }
1558
+ /** The thread id currently associated with this chat, if any. */
1559
+ get threadId() {
1560
+ return this.config.threadId;
1561
+ }
1558
1562
  // ============================================
1559
1563
  // Public Actions
1560
1564
  // ============================================
@@ -2200,6 +2204,7 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2200
2204
  this.debug("Starting to process stream");
2201
2205
  let chunkCount = 0;
2202
2206
  let toolCallsEmitted = false;
2207
+ let postToolTextStreamed = false;
2203
2208
  let pendingClientToolCalls;
2204
2209
  for await (const chunk of stream) {
2205
2210
  chunkCount++;
@@ -2256,15 +2261,27 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2256
2261
  continue;
2257
2262
  }
2258
2263
  if (!this.streamState) {
2259
- if (chunk.type === "tool_calls") {
2264
+ if (chunk.type === "message:delta") {
2265
+ this.debug(
2266
+ "message:delta with no streamState \u2014 auto-init new message"
2267
+ );
2268
+ const currentLeaf = this.state.messages;
2269
+ const currentLeafId = currentLeaf.length > 0 ? currentLeaf[currentLeaf.length - 1].id : void 0;
2270
+ const newMessage = createEmptyAssistantMessage(void 0, {
2271
+ parentId: currentLeafId
2272
+ });
2273
+ this.state.pushMessage(newMessage);
2274
+ this.streamState = createStreamState(newMessage.id);
2275
+ this.callbacks.onMessageStart?.(newMessage.id);
2276
+ postToolTextStreamed = true;
2277
+ } else if (chunk.type === "tool_calls") {
2260
2278
  pendingClientToolCalls = chunk.toolCalls;
2261
2279
  this.debug("tool_calls (post-message:end, stored as pending)", {
2262
2280
  count: pendingClientToolCalls?.length,
2263
2281
  ids: pendingClientToolCalls?.map((tc) => tc.id)
2264
2282
  });
2265
2283
  continue;
2266
- }
2267
- if (chunk.type === "done") {
2284
+ } else if (chunk.type === "done") {
2268
2285
  this.debug("done (post-message:end)", {
2269
2286
  hasPendingToolCalls: !!pendingClientToolCalls?.length,
2270
2287
  pendingCount: pendingClientToolCalls?.length ?? 0,
@@ -2356,9 +2373,10 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2356
2373
  });
2357
2374
  }
2358
2375
  continue;
2376
+ } else {
2377
+ this.debug("warning", "streamState is null, skipping chunk");
2378
+ continue;
2359
2379
  }
2360
- this.debug("warning", "streamState is null, skipping chunk");
2361
- continue;
2362
2380
  }
2363
2381
  this.streamState = processStreamChunk(chunk, this.streamState);
2364
2382
  if (chunk.type === "action:start") {
@@ -2454,9 +2472,12 @@ ${this.dynamicContext}`.trim() : this.config.systemPrompt;
2454
2472
  }
2455
2473
  }
2456
2474
  }
2475
+ let seenToolResult = false;
2457
2476
  for (const msg of chunk.messages) {
2477
+ if (msg.role === "tool") seenToolResult = true;
2458
2478
  if (msg.role === "assistant" && !msg.tool_calls?.length) {
2459
- continue;
2479
+ if (!seenToolResult) continue;
2480
+ if (postToolTextStreamed) continue;
2460
2481
  }
2461
2482
  if (msg.role === "assistant" && msg.tool_calls?.length && msg.tool_calls.every(
2462
2483
  (toolCall) => currentStreamToolCallIds.has(toolCall.id)
@@ -2984,6 +3005,7 @@ var AbstractAgentLoop = class {
2984
3005
  }
2985
3006
  const result = await tool.handler(toolCall.args, {
2986
3007
  signal: this.abortController?.signal,
3008
+ threadId: this.config.getThreadId?.(),
2987
3009
  data: { toolCallId: toolCall.id },
2988
3010
  approvalData
2989
3011
  });
@@ -3511,7 +3533,13 @@ var ChatWithTools = class {
3511
3533
  this.agentLoop = new AbstractAgentLoop(
3512
3534
  {
3513
3535
  maxIterations: config.maxIterations ?? 20,
3514
- tools: config.tools
3536
+ tools: config.tools,
3537
+ // Expose this chat's current threadId to tool handlers via
3538
+ // ToolContext.threadId. Read lazily per invocation so it reflects
3539
+ // the id assigned by the server mid-stream (thread:created).
3540
+ // If the caller provided a getThreadId override (e.g. the React
3541
+ // provider exposing its stable registry key), prefer that.
3542
+ getThreadId: config.getThreadId ? () => config.getThreadId() : () => this.chat?.threadId
3515
3543
  },
3516
3544
  {
3517
3545
  onExecutionsChange: (executions) => {
@@ -5421,7 +5449,8 @@ function CopilotProvider(props) {
5421
5449
  mcpServers,
5422
5450
  optimization,
5423
5451
  messageHistory,
5424
- skills
5452
+ skills,
5453
+ concurrentThreads = false
5425
5454
  } = props;
5426
5455
  const isThreadIdControlled = Object.prototype.hasOwnProperty.call(
5427
5456
  props,
@@ -5459,87 +5488,350 @@ function CopilotProvider(props) {
5459
5488
  controlled: isThreadIdControlled,
5460
5489
  value: threadId
5461
5490
  });
5491
+ const SINGLE_INSTANCE_KEY = "__single__";
5462
5492
  const chatRef = useRef(null);
5463
- if (chatRef.current !== null && chatRef.current.disposed) {
5464
- chatRef.current.revive();
5465
- debugLog("Revived disposed instance (React StrictMode)");
5466
- }
5467
- if (chatRef.current === null) {
5468
- const uiInitialMessages = initialMessages;
5469
- chatRef.current = new ReactChatWithTools(
5470
- {
5471
- runtimeUrl,
5472
- systemPrompt,
5473
- threadId,
5474
- onCreateSession,
5475
- yourgptConfig,
5476
- initialMessages: uiInitialMessages,
5477
- streaming,
5478
- headers,
5479
- body,
5480
- parseError,
5481
- debug,
5482
- maxIterations,
5483
- maxIterationsMessage,
5484
- optimization
5485
- },
5486
- {
5487
- onToolExecutionsChange: (executions) => {
5488
- debugLog("Tool executions changed:", executions.length);
5489
- setToolExecutions(executions);
5490
- setAgentIteration(chatRef.current?.iteration ?? 0);
5491
- },
5492
- onApprovalRequired: (execution) => {
5493
- debugLog("Tool approval required:", execution.name);
5494
- },
5495
- onContextUsageChange: (usage) => {
5496
- setContextUsage(usage);
5497
- },
5498
- onError: (error2) => {
5499
- if (error2) onError?.(error2);
5500
- },
5501
- onThreadChange: (id) => {
5502
- debugLog("Thread/session ID assigned:", id);
5503
- setActualThreadId(id);
5504
- onThreadChange?.(id);
5505
- },
5506
- onSessionStatusChange: (status2) => {
5507
- debugLog("Session status:", status2);
5508
- setSessionStatus(status2);
5493
+ const instancesRef = useRef(/* @__PURE__ */ new Map());
5494
+ const activeInstanceKeyRef = useRef(SINGLE_INSTANCE_KEY);
5495
+ const pendingCounterRef = useRef(0);
5496
+ const sharedToolsRef = useRef(/* @__PURE__ */ new Map());
5497
+ const sharedSkillsRef = useRef([]);
5498
+ const sharedSystemContextRef = useRef("");
5499
+ const subscribersRef = useRef(/* @__PURE__ */ new Set());
5500
+ const stableSubscribe = useMemo(
5501
+ () => (cb) => {
5502
+ subscribersRef.current.add(cb);
5503
+ return () => {
5504
+ subscribersRef.current.delete(cb);
5505
+ };
5506
+ },
5507
+ []
5508
+ );
5509
+ const [busyThreadIds, setBusyThreadIds] = useState(
5510
+ () => /* @__PURE__ */ new Set()
5511
+ );
5512
+ const recomputeBusyThreadIds = useCallback(() => {
5513
+ if (!concurrentThreads) return;
5514
+ const next = /* @__PURE__ */ new Set();
5515
+ for (const [key, inst] of instancesRef.current) {
5516
+ if (key === SINGLE_INSTANCE_KEY) continue;
5517
+ if (key.startsWith("__pending_")) continue;
5518
+ const s = inst.status;
5519
+ if (s === "streaming" || s === "submitted") next.add(key);
5520
+ }
5521
+ setBusyThreadIds((prev) => {
5522
+ if (prev.size === next.size) {
5523
+ let same = true;
5524
+ for (const id of next) {
5525
+ if (!prev.has(id)) {
5526
+ same = false;
5527
+ break;
5528
+ }
5529
+ }
5530
+ if (same) return prev;
5531
+ }
5532
+ return next;
5533
+ });
5534
+ }, [concurrentThreads]);
5535
+ const [pendingApprovalsByThread, setPendingApprovalsByThread] = useState(() => /* @__PURE__ */ new Map());
5536
+ const recomputePendingApprovalsByThread = useCallback(() => {
5537
+ if (!concurrentThreads) return;
5538
+ const next = /* @__PURE__ */ new Map();
5539
+ for (const [key, inst] of instancesRef.current) {
5540
+ if (key === SINGLE_INSTANCE_KEY) continue;
5541
+ if (key.startsWith("__pending_")) continue;
5542
+ const pending = inst.toolExecutions.filter(
5543
+ (e) => e.approvalStatus === "required"
5544
+ );
5545
+ if (pending.length > 0) next.set(key, pending);
5546
+ }
5547
+ setPendingApprovalsByThread((prev) => {
5548
+ if (prev.size !== next.size) return next;
5549
+ for (const [id, execs] of next) {
5550
+ const prevExecs = prev.get(id);
5551
+ if (!prevExecs || prevExecs.length !== execs.length) return next;
5552
+ for (let i = 0; i < execs.length; i++) {
5553
+ if (prevExecs[i] !== execs[i]) return next;
5554
+ }
5555
+ }
5556
+ return prev;
5557
+ });
5558
+ }, [concurrentThreads]);
5559
+ const notifyStateChange = useCallback(() => {
5560
+ for (const cb of subscribersRef.current) cb();
5561
+ recomputeBusyThreadIds();
5562
+ recomputePendingApprovalsByThread();
5563
+ }, [recomputeBusyThreadIds, recomputePendingApprovalsByThread]);
5564
+ const configRef = useRef({
5565
+ runtimeUrl,
5566
+ systemPrompt,
5567
+ threadId,
5568
+ onCreateSession,
5569
+ yourgptConfig,
5570
+ initialMessages,
5571
+ streaming,
5572
+ headers,
5573
+ body,
5574
+ parseError,
5575
+ debug,
5576
+ maxIterations,
5577
+ maxIterationsMessage,
5578
+ optimization
5579
+ });
5580
+ configRef.current = {
5581
+ runtimeUrl,
5582
+ systemPrompt,
5583
+ threadId,
5584
+ onCreateSession,
5585
+ yourgptConfig,
5586
+ initialMessages,
5587
+ streaming,
5588
+ headers,
5589
+ body,
5590
+ parseError,
5591
+ debug,
5592
+ maxIterations,
5593
+ maxIterationsMessage,
5594
+ optimization
5595
+ };
5596
+ const callbacksRef = useRef({ onError, onThreadChange });
5597
+ callbacksRef.current = { onError, onThreadChange };
5598
+ const handleInstanceThreadAssigned = useCallback(
5599
+ (inst, newId) => {
5600
+ let oldKey;
5601
+ for (const [k, v] of instancesRef.current) {
5602
+ if (v === inst) {
5603
+ oldKey = k;
5604
+ break;
5605
+ }
5606
+ }
5607
+ if (oldKey === void 0) return;
5608
+ const isInternalKey = oldKey === SINGLE_INSTANCE_KEY || oldKey.startsWith("__pending_");
5609
+ const shouldRekey = !concurrentThreads || isInternalKey;
5610
+ if (shouldRekey && oldKey !== newId && !instancesRef.current.has(newId)) {
5611
+ instancesRef.current.delete(oldKey);
5612
+ instancesRef.current.set(newId, inst);
5613
+ if (activeInstanceKeyRef.current === oldKey) {
5614
+ activeInstanceKeyRef.current = newId;
5615
+ }
5616
+ if (inst === chatRef.current) {
5617
+ debugLog("Thread/session ID assigned:", newId);
5618
+ setActualThreadId(newId);
5619
+ callbacksRef.current.onThreadChange?.(newId);
5620
+ }
5621
+ }
5622
+ recomputeBusyThreadIds();
5623
+ recomputePendingApprovalsByThread();
5624
+ },
5625
+ [
5626
+ concurrentThreads,
5627
+ debugLog,
5628
+ recomputeBusyThreadIds,
5629
+ recomputePendingApprovalsByThread
5630
+ ]
5631
+ );
5632
+ const createInstance = useCallback(
5633
+ (key, opts) => {
5634
+ const cfg = configRef.current;
5635
+ const initialThreadId = key === SINGLE_INSTANCE_KEY || key.startsWith("__pending_") ? cfg.threadId : key;
5636
+ const inst = new ReactChatWithTools(
5637
+ {
5638
+ runtimeUrl: cfg.runtimeUrl,
5639
+ systemPrompt: cfg.systemPrompt,
5640
+ threadId: initialThreadId,
5641
+ onCreateSession: cfg.onCreateSession,
5642
+ yourgptConfig: cfg.yourgptConfig,
5643
+ initialMessages: opts?.initialMessages ?? cfg.initialMessages,
5644
+ streaming: cfg.streaming,
5645
+ headers: cfg.headers,
5646
+ body: cfg.body,
5647
+ parseError: cfg.parseError,
5648
+ debug: cfg.debug,
5649
+ maxIterations: cfg.maxIterations,
5650
+ maxIterationsMessage: cfg.maxIterationsMessage,
5651
+ optimization: cfg.optimization,
5652
+ // Expose the registry key (usually the UI thread id after
5653
+ // assignLocalThreadId runs) to tool handlers, NOT chat.config.threadId.
5654
+ // For extensions that key per-thread state on context.threadId (e.g.
5655
+ // browser-tab pinning), this guarantees a stable id — even while the
5656
+ // backend session id is still being resolved. Internal keys
5657
+ // (__single__, __pending_*) return undefined so handlers can treat
5658
+ // them as "not yet committed".
5659
+ getThreadId: () => {
5660
+ for (const [k, v] of instancesRef.current) {
5661
+ if (v === inst) {
5662
+ if (k === SINGLE_INSTANCE_KEY || k.startsWith("__pending_")) {
5663
+ return void 0;
5664
+ }
5665
+ return k;
5666
+ }
5667
+ }
5668
+ return void 0;
5669
+ }
5509
5670
  },
5510
- onStreamChunk: (chunk) => {
5511
- if (streamListenersRef.current.size > 0) {
5512
- for (const handler of streamListenersRef.current) {
5513
- handler(chunk);
5671
+ {
5672
+ onToolExecutionsChange: (executions) => {
5673
+ debugLog("Tool executions changed:", executions.length);
5674
+ if (inst === chatRef.current) {
5675
+ setToolExecutions(executions);
5676
+ setAgentIteration(inst.iteration ?? 0);
5677
+ }
5678
+ recomputePendingApprovalsByThread();
5679
+ },
5680
+ onApprovalRequired: (execution) => {
5681
+ debugLog("Tool approval required:", execution.name);
5682
+ recomputePendingApprovalsByThread();
5683
+ },
5684
+ onContextUsageChange: (usage) => {
5685
+ if (inst === chatRef.current) {
5686
+ setContextUsage(usage);
5687
+ }
5688
+ },
5689
+ onError: (error2) => {
5690
+ if (error2 && inst === chatRef.current) {
5691
+ callbacksRef.current.onError?.(error2);
5692
+ }
5693
+ },
5694
+ onThreadChange: (id) => {
5695
+ handleInstanceThreadAssigned(inst, id);
5696
+ },
5697
+ onSessionStatusChange: (status2) => {
5698
+ debugLog("Session status:", status2);
5699
+ if (inst === chatRef.current) {
5700
+ setSessionStatus(status2);
5701
+ }
5702
+ },
5703
+ onStreamChunk: (chunk) => {
5704
+ if (streamListenersRef.current.size > 0) {
5705
+ for (const handler of streamListenersRef.current) {
5706
+ handler(chunk);
5707
+ }
5514
5708
  }
5515
5709
  }
5516
5710
  }
5711
+ );
5712
+ for (const { tool } of sharedToolsRef.current.values()) {
5713
+ inst.registerTool(tool);
5517
5714
  }
5518
- );
5715
+ if (sharedSkillsRef.current.length > 0) {
5716
+ inst.setInlineSkills(sharedSkillsRef.current);
5717
+ }
5718
+ if (sharedSystemContextRef.current) {
5719
+ inst.setContext(sharedSystemContextRef.current);
5720
+ }
5721
+ inst.subscribe(notifyStateChange);
5722
+ instancesRef.current.set(key, inst);
5723
+ return inst;
5724
+ },
5725
+ [
5726
+ debugLog,
5727
+ handleInstanceThreadAssigned,
5728
+ notifyStateChange,
5729
+ recomputePendingApprovalsByThread
5730
+ ]
5731
+ );
5732
+ if (chatRef.current !== null && chatRef.current.disposed) {
5733
+ for (const inst of instancesRef.current.values()) {
5734
+ inst.revive();
5735
+ inst.subscribe(notifyStateChange);
5736
+ }
5737
+ debugLog("Revived disposed instance(s) (React StrictMode)");
5519
5738
  }
5739
+ if (chatRef.current === null) {
5740
+ const initialKey = concurrentThreads && threadId ? threadId : SINGLE_INSTANCE_KEY;
5741
+ activeInstanceKeyRef.current = initialKey;
5742
+ chatRef.current = createInstance(initialKey);
5743
+ }
5744
+ const switchActiveInstance = useCallback(
5745
+ (key, opts) => {
5746
+ if (!concurrentThreads) {
5747
+ chatRef.current?.setActiveThread(key);
5748
+ return;
5749
+ }
5750
+ let targetKey;
5751
+ if (key == null) {
5752
+ const currentKey = activeInstanceKeyRef.current;
5753
+ const currentInst = instancesRef.current.get(currentKey);
5754
+ const isCurrentFreshEmpty = currentInst !== void 0 && currentInst.messages.length === 0 && (currentKey === SINGLE_INSTANCE_KEY || currentKey.startsWith("__pending_"));
5755
+ if (isCurrentFreshEmpty) {
5756
+ return;
5757
+ }
5758
+ targetKey = `__pending_${++pendingCounterRef.current}__`;
5759
+ } else {
5760
+ targetKey = key;
5761
+ }
5762
+ let inst = instancesRef.current.get(targetKey);
5763
+ const wasFresh = !inst;
5764
+ if (!inst) {
5765
+ const currentKey = activeInstanceKeyRef.current;
5766
+ const currentInst = instancesRef.current.get(currentKey);
5767
+ const currentIsPromotableSlot = currentInst !== void 0 && currentInst.messages.length === 0 && (currentKey === SINGLE_INSTANCE_KEY || currentKey.startsWith("__pending_"));
5768
+ if (currentIsPromotableSlot) {
5769
+ instancesRef.current.delete(currentKey);
5770
+ instancesRef.current.set(targetKey, currentInst);
5771
+ currentInst.setActiveThread(targetKey);
5772
+ inst = currentInst;
5773
+ if (opts?.hydrateMessages) {
5774
+ inst.setMessages(opts.hydrateMessages);
5775
+ }
5776
+ if (opts?.hydrateActiveLeafId) {
5777
+ inst.switchBranch(opts.hydrateActiveLeafId);
5778
+ }
5779
+ } else {
5780
+ inst = createInstance(targetKey, {
5781
+ initialMessages: opts?.hydrateMessages
5782
+ });
5783
+ if (opts?.hydrateActiveLeafId) {
5784
+ inst.switchBranch(opts.hydrateActiveLeafId);
5785
+ }
5786
+ }
5787
+ } else if (wasFresh && opts?.hydrateMessages && inst.messages.length === 0) {
5788
+ inst.setMessages(opts.hydrateMessages);
5789
+ if (opts.hydrateActiveLeafId)
5790
+ inst.switchBranch(opts.hydrateActiveLeafId);
5791
+ }
5792
+ if (activeInstanceKeyRef.current === targetKey) return;
5793
+ activeInstanceKeyRef.current = targetKey;
5794
+ chatRef.current = inst;
5795
+ if (targetKey === SINGLE_INSTANCE_KEY || targetKey.startsWith("__pending_")) {
5796
+ setActualThreadId(void 0);
5797
+ } else {
5798
+ setActualThreadId(targetKey);
5799
+ }
5800
+ setSessionStatus(inst.getSessionStatus());
5801
+ setToolExecutions(inst.toolExecutions);
5802
+ setAgentIteration(inst.iteration);
5803
+ notifyStateChange();
5804
+ debugLog("Active instance switched", { key: targetKey });
5805
+ },
5806
+ [concurrentThreads, createInstance, debugLog, notifyStateChange]
5807
+ );
5520
5808
  useEffect(() => {
5521
- if (chatRef.current && systemPrompt !== void 0) {
5522
- chatRef.current.setSystemPrompt(systemPrompt);
5523
- debugLog("System prompt updated from prop");
5809
+ if (systemPrompt === void 0) return;
5810
+ for (const inst of instancesRef.current.values()) {
5811
+ inst.setSystemPrompt(systemPrompt);
5524
5812
  }
5813
+ debugLog("System prompt updated from prop");
5525
5814
  }, [systemPrompt, debugLog]);
5526
5815
  useEffect(() => {
5527
- if (chatRef.current && headers !== void 0) {
5528
- chatRef.current.setHeaders(headers);
5529
- debugLog("Headers config updated from prop");
5816
+ if (headers === void 0) return;
5817
+ for (const inst of instancesRef.current.values()) {
5818
+ inst.setHeaders(headers);
5530
5819
  }
5820
+ debugLog("Headers config updated from prop");
5531
5821
  }, [headers, debugLog]);
5532
5822
  useEffect(() => {
5533
- if (chatRef.current && body !== void 0) {
5534
- chatRef.current.setBody(body);
5535
- debugLog("Body config updated from prop");
5823
+ if (body === void 0) return;
5824
+ for (const inst of instancesRef.current.values()) {
5825
+ inst.setBody(body);
5536
5826
  }
5827
+ debugLog("Body config updated from prop");
5537
5828
  }, [body, debugLog]);
5538
5829
  useEffect(() => {
5539
- if (chatRef.current && runtimeUrl !== void 0) {
5540
- chatRef.current.setUrl(runtimeUrl);
5541
- debugLog("URL config updated from prop");
5830
+ if (runtimeUrl === void 0) return;
5831
+ for (const inst of instancesRef.current.values()) {
5832
+ inst.setUrl(runtimeUrl);
5542
5833
  }
5834
+ debugLog("URL config updated from prop");
5543
5835
  }, [runtimeUrl, debugLog]);
5544
5836
  useEffect(() => {
5545
5837
  const prev = lastControlledThreadIdRef.current;
@@ -5555,11 +5847,21 @@ function CopilotProvider(props) {
5555
5847
  if (!isThreadIdControlled) {
5556
5848
  return;
5557
5849
  }
5558
- chatRef.current?.setActiveThread(threadId ?? null);
5559
- setActualThreadId(threadId);
5560
- setSessionStatus(threadId ? "ready" : "idle");
5850
+ if (concurrentThreads) {
5851
+ switchActiveInstance(threadId ?? null);
5852
+ } else {
5853
+ chatRef.current?.setActiveThread(threadId ?? null);
5854
+ setActualThreadId(threadId);
5855
+ setSessionStatus(threadId ? "ready" : "idle");
5856
+ }
5561
5857
  debugLog("Thread/session synced from prop", { threadId });
5562
- }, [debugLog, isThreadIdControlled, threadId]);
5858
+ }, [
5859
+ debugLog,
5860
+ isThreadIdControlled,
5861
+ threadId,
5862
+ concurrentThreads,
5863
+ switchActiveInstance
5864
+ ]);
5563
5865
  const EMPTY_MESSAGES = useRef([]);
5564
5866
  const getMessagesSnapshot = useCallback(() => chatRef.current.messages, []);
5565
5867
  const getServerMessagesSnapshot = useCallback(
@@ -5569,36 +5871,110 @@ function CopilotProvider(props) {
5569
5871
  const getStatusSnapshot = useCallback(() => chatRef.current.status, []);
5570
5872
  const getErrorSnapshot = useCallback(() => chatRef.current.error, []);
5571
5873
  const messages = useSyncExternalStore(
5572
- chatRef.current.subscribe,
5874
+ stableSubscribe,
5573
5875
  getMessagesSnapshot,
5574
5876
  getServerMessagesSnapshot
5575
5877
  );
5576
5878
  const status = useSyncExternalStore(
5577
- chatRef.current.subscribe,
5879
+ stableSubscribe,
5578
5880
  getStatusSnapshot,
5579
5881
  () => "ready"
5580
5882
  );
5581
5883
  const errorFromChat = useSyncExternalStore(
5582
- chatRef.current.subscribe,
5884
+ stableSubscribe,
5583
5885
  getErrorSnapshot,
5584
5886
  () => void 0
5585
5887
  );
5586
5888
  const error = errorFromChat ?? null;
5587
5889
  const isLoading = status === "streaming" || status === "submitted";
5588
- const setActiveThread = useCallback((id) => {
5589
- chatRef.current?.setActiveThread(id);
5590
- setActualThreadId(id ?? void 0);
5591
- }, []);
5890
+ const setActiveThread = useCallback(
5891
+ (id, opts) => {
5892
+ if (concurrentThreads) {
5893
+ switchActiveInstance(id, opts);
5894
+ } else {
5895
+ chatRef.current?.setActiveThread(id);
5896
+ setActualThreadId(id ?? void 0);
5897
+ }
5898
+ },
5899
+ [concurrentThreads, switchActiveInstance]
5900
+ );
5901
+ const disposeThreadInstance = useCallback(
5902
+ (id) => {
5903
+ if (!concurrentThreads) return;
5904
+ const inst = instancesRef.current.get(id);
5905
+ if (!inst) return;
5906
+ inst.dispose();
5907
+ instancesRef.current.delete(id);
5908
+ if (activeInstanceKeyRef.current === id) {
5909
+ switchActiveInstance(null);
5910
+ } else {
5911
+ recomputeBusyThreadIds();
5912
+ recomputePendingApprovalsByThread();
5913
+ }
5914
+ },
5915
+ [
5916
+ concurrentThreads,
5917
+ switchActiveInstance,
5918
+ recomputeBusyThreadIds,
5919
+ recomputePendingApprovalsByThread
5920
+ ]
5921
+ );
5922
+ const assignLocalThreadId = useCallback(
5923
+ (localId) => {
5924
+ if (!concurrentThreads) return;
5925
+ if (!localId) return;
5926
+ const currentKey = activeInstanceKeyRef.current;
5927
+ if (currentKey === localId) return;
5928
+ const currentInst = instancesRef.current.get(currentKey);
5929
+ if (!currentInst) return;
5930
+ const isInternalSlot = currentKey === SINGLE_INSTANCE_KEY || currentKey.startsWith("__pending_");
5931
+ if (!isInternalSlot) return;
5932
+ if (instancesRef.current.has(localId)) return;
5933
+ instancesRef.current.delete(currentKey);
5934
+ instancesRef.current.set(localId, currentInst);
5935
+ activeInstanceKeyRef.current = localId;
5936
+ if (currentInst === chatRef.current) {
5937
+ setActualThreadId(localId);
5938
+ callbacksRef.current.onThreadChange?.(localId);
5939
+ }
5940
+ recomputeBusyThreadIds();
5941
+ recomputePendingApprovalsByThread();
5942
+ debugLog("Assigned local thread id", { localId });
5943
+ },
5944
+ [
5945
+ concurrentThreads,
5946
+ debugLog,
5947
+ recomputeBusyThreadIds,
5948
+ recomputePendingApprovalsByThread
5949
+ ]
5950
+ );
5592
5951
  const renewSession = useCallback(() => {
5593
5952
  chatRef.current?.renewSession();
5594
5953
  setActualThreadId(void 0);
5595
5954
  setSessionStatus("idle");
5596
5955
  }, []);
5597
5956
  const registerTool = useCallback((tool) => {
5598
- chatRef.current?.registerTool(tool);
5957
+ const existing = sharedToolsRef.current.get(tool.name);
5958
+ if (existing) {
5959
+ existing.tool = tool;
5960
+ existing.refCount++;
5961
+ } else {
5962
+ sharedToolsRef.current.set(tool.name, { tool, refCount: 1 });
5963
+ }
5964
+ for (const inst of instancesRef.current.values()) {
5965
+ inst.registerTool(tool);
5966
+ }
5599
5967
  }, []);
5600
5968
  const unregisterTool = useCallback((name) => {
5601
- chatRef.current?.unregisterTool(name);
5969
+ const entry = sharedToolsRef.current.get(name);
5970
+ if (!entry) return;
5971
+ entry.refCount = Math.max(0, entry.refCount - 1);
5972
+ for (const inst of instancesRef.current.values()) {
5973
+ inst.unregisterTool(name);
5974
+ }
5975
+ if (entry.refCount === 0) {
5976
+ sharedToolsRef.current.delete(name);
5977
+ }
5602
5978
  }, []);
5603
5979
  const approveToolExecution = useCallback(
5604
5980
  (id, extraData, permissionLevel) => {
@@ -5649,6 +6025,7 @@ function CopilotProvider(props) {
5649
6025
  parentId
5650
6026
  );
5651
6027
  const contextString = printTree(contextTreeRef.current);
6028
+ sharedSystemContextRef.current = contextString;
5652
6029
  chatRef.current?.setContext(contextString);
5653
6030
  setContextChars(contextString.length);
5654
6031
  debugLog("Context added:", id);
@@ -5660,6 +6037,7 @@ function CopilotProvider(props) {
5660
6037
  (id) => {
5661
6038
  contextTreeRef.current = removeNode(contextTreeRef.current, id);
5662
6039
  const contextString = printTree(contextTreeRef.current);
6040
+ sharedSystemContextRef.current = contextString;
5663
6041
  chatRef.current?.setContext(contextString);
5664
6042
  setContextChars(contextString.length);
5665
6043
  debugLog("Context removed:", id);
@@ -5668,14 +6046,19 @@ function CopilotProvider(props) {
5668
6046
  );
5669
6047
  const setSystemPrompt = useCallback(
5670
6048
  (prompt) => {
5671
- chatRef.current?.setSystemPrompt(prompt);
6049
+ for (const inst of instancesRef.current.values()) {
6050
+ inst.setSystemPrompt(prompt);
6051
+ }
5672
6052
  debugLog("System prompt updated via function");
5673
6053
  },
5674
6054
  [debugLog]
5675
6055
  );
5676
6056
  const setInlineSkills = useCallback(
5677
6057
  (skills2) => {
5678
- chatRef.current?.setInlineSkills(skills2);
6058
+ sharedSkillsRef.current = skills2;
6059
+ for (const inst of instancesRef.current.values()) {
6060
+ inst.setInlineSkills(skills2);
6061
+ }
5679
6062
  debugLog("Inline skills updated", { count: skills2.length });
5680
6063
  },
5681
6064
  [debugLog]
@@ -5723,7 +6106,7 @@ function CopilotProvider(props) {
5723
6106
  []
5724
6107
  );
5725
6108
  const hasBranches = useSyncExternalStore(
5726
- chatRef.current.subscribe,
6109
+ stableSubscribe,
5727
6110
  getHasBranchesSnapshot,
5728
6111
  () => false
5729
6112
  );
@@ -5758,7 +6141,9 @@ function CopilotProvider(props) {
5758
6141
  }, [error, onError]);
5759
6142
  useEffect(() => {
5760
6143
  return () => {
5761
- chatRef.current?.dispose();
6144
+ for (const inst of instancesRef.current.values()) {
6145
+ inst.dispose();
6146
+ }
5762
6147
  };
5763
6148
  }, []);
5764
6149
  const contextValue = useMemo(
@@ -5812,7 +6197,13 @@ function CopilotProvider(props) {
5812
6197
  toolsConfig,
5813
6198
  // Headless primitives
5814
6199
  subscribeToStreamEvents,
5815
- messageMeta: messageMetaStoreRef.current
6200
+ messageMeta: messageMetaStoreRef.current,
6201
+ // Multi-thread streaming
6202
+ concurrentThreads,
6203
+ busyThreadIds,
6204
+ pendingApprovalsByThread,
6205
+ disposeThreadInstance,
6206
+ assignLocalThreadId
5816
6207
  }),
5817
6208
  [
5818
6209
  messages,
@@ -5852,7 +6243,12 @@ function CopilotProvider(props) {
5852
6243
  renewSession,
5853
6244
  sessionStatus,
5854
6245
  runtimeUrl,
5855
- toolsConfig
6246
+ toolsConfig,
6247
+ concurrentThreads,
6248
+ busyThreadIds,
6249
+ pendingApprovalsByThread,
6250
+ disposeThreadInstance,
6251
+ assignLocalThreadId
5856
6252
  ]
5857
6253
  );
5858
6254
  const messageHistoryContextValue = React2.useMemo(
@@ -5958,5 +6354,5 @@ function useMCPTools(config) {
5958
6354
  }
5959
6355
 
5960
6356
  export { AbstractAgentLoop, AbstractChat, CopilotProvider, MessageHistoryContext, MessageTree, ReactChatState, SkillProvider, createReactChatState, defaultMessageHistoryConfig, initialAgentLoopState, isCompactionMarker, keepToolPairsAtomic, toDisplayMessage, toLLMMessage, toLLMMessages, useAIContext, useAIContexts, useCopilot, useMCPClient, useMCPTools, useMessageHistory, useMessageHistoryContext, useSkillContext, useTool, useTools };
5961
- //# sourceMappingURL=chunk-JPUKXFR4.js.map
5962
- //# sourceMappingURL=chunk-JPUKXFR4.js.map
6357
+ //# sourceMappingURL=chunk-37HN4F6S.js.map
6358
+ //# sourceMappingURL=chunk-37HN4F6S.js.map