@copilotkit/core 1.77.0 → 1.77.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -196,10 +196,17 @@ function ɵcreatePhoenixJoinOutcome$(channel) {
196
196
  *
197
197
  * The socket is constructed and connected on subscription, and disconnected on
198
198
  * teardown. Each subscription creates an isolated socket instance.
199
+ *
200
+ * Heartbeats default to every 15s: Phoenix's own 30s default matches common
201
+ * reverse-proxy WebSocket idle timeouts (e.g. Azure Application Gateway), which
202
+ * would drop the socket during a quiet run. Callers may override it.
199
203
  */
200
204
  function ɵphoenixSocket$(options) {
201
205
  return (0, rxjs.defer)(() => {
202
- const socket = new phoenix.Socket(options.url, options.options);
206
+ const socket = new phoenix.Socket(options.url, {
207
+ heartbeatIntervalMs: 15e3,
208
+ ...options.options
209
+ });
203
210
  const signals$ = ɵcreatePhoenixSocketSignals$(socket).pipe((0, rxjs_operators.shareReplay)({
204
211
  bufferSize: 1,
205
212
  refCount: true
@@ -283,6 +290,11 @@ const STOP_RUN_EVENT = "stop_run";
283
290
  const CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS = 100;
284
291
  const CONNECT_IDLE_WARNING_MS = 3e4;
285
292
  const MAX_UNOPENED_CREDENTIAL_REFRESHES = 2;
293
+ function isMcpResourceRead(input) {
294
+ const forwardedProps = input.forwardedProps;
295
+ const request = forwardedProps?.__proxiedMCPRequest;
296
+ return forwardedProps?.__copilotkitMcpResourceReadOnly === true && typeof request === "object" && request !== null && request.method === "resources/read";
297
+ }
286
298
  var AgentThreadLockedError = class extends Error {
287
299
  constructor(threadId) {
288
300
  super(threadId ? `Thread ${threadId} is locked` : "Thread is locked");
@@ -390,10 +402,22 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
390
402
  * server-pushed AG-UI events to the Observable subscriber.
391
403
  */
392
404
  run(input) {
393
- this.threadId = input.threadId;
394
- this.canonicalRunId = input.runId;
405
+ if (!isMcpResourceRead(input)) {
406
+ this.threadId = input.threadId;
407
+ this.canonicalRunId = input.runId;
408
+ }
395
409
  return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => {
396
410
  if (credentials === null) return (0, rxjs.throwError)(() => /* @__PURE__ */ new Error("REST run request returned no credentials"));
411
+ if ("kind" in credentials) return (0, rxjs.of)({
412
+ type: _ag_ui_client.EventType.RUN_STARTED,
413
+ threadId: credentials.threadId,
414
+ runId: credentials.runId
415
+ }, {
416
+ type: _ag_ui_client.EventType.RUN_FINISHED,
417
+ threadId: credentials.threadId,
418
+ runId: credentials.runId,
419
+ result: credentials.result
420
+ });
397
421
  const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: true });
398
422
  return this.observeThread$(canonicalInput, credentials, {
399
423
  completeOnRunError: false,
@@ -485,7 +509,18 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
485
509
  const text = await response.text().catch(() => "");
486
510
  throw new Error(text || response.statusText || String(response.status));
487
511
  }
488
- return this.normalizeJoinCredentials(await response.json(), input);
512
+ const payload = await response.json();
513
+ if (mode === "run" && isMcpResourceRead(input) && typeof payload === "object" && payload !== null && payload.kind === "mcp-resource-read") {
514
+ const resource = payload;
515
+ if (typeof resource.threadId !== "string" || !resource.threadId || typeof resource.runId !== "string" || !resource.runId) throw new Error("invalid MCP resource response identity");
516
+ return {
517
+ kind: "mcp-resource-read",
518
+ threadId: resource.threadId,
519
+ runId: resource.runId,
520
+ result: resource.result
521
+ };
522
+ }
523
+ return this.normalizeJoinCredentials(payload, input);
489
524
  } catch (error) {
490
525
  if (error instanceof AgentThreadLockedError) throw error;
491
526
  throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
@@ -1268,6 +1303,21 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1268
1303
  }
1269
1304
  return cloned;
1270
1305
  }
1306
+ /** Run presentation-only MCP reads on a separate client agent so their
1307
+ * lifecycle cannot replace an in-flight approval's detach/completion state. */
1308
+ async ɵrunMcpResourceRead(parameters) {
1309
+ const request = parameters.forwardedProps?.__proxiedMCPRequest;
1310
+ if (typeof request !== "object" || request === null || request.method !== "resources/read") throw new Error("Only MCP resources/read can use the read-only path");
1311
+ await this.ensureRuntimeConfiguration();
1312
+ const resourceParameters = {
1313
+ ...parameters,
1314
+ forwardedProps: {
1315
+ ...parameters.forwardedProps,
1316
+ __copilotkitMcpResourceReadOnly: true
1317
+ }
1318
+ };
1319
+ return this.runtimeMode === _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE ? this.clone().runAgent(resourceParameters) : this.runAgent(resourceParameters);
1320
+ }
1271
1321
  /**
1272
1322
  * Drop the delegate's cached `lastSeenEventId` for this thread so
1273
1323
  * the next connect requests a full historical replay from the
@@ -3489,7 +3539,7 @@ var RunHandler = class {
3489
3539
  this.processAgentResult({
3490
3540
  runAgentResult,
3491
3541
  agent,
3492
- toolExecutionMode: "human-in-the-loop",
3542
+ toolExecutionMode: "restore",
3493
3543
  signal: controller.signal
3494
3544
  }).catch(async (error) => {
3495
3545
  await this._internal.emitError({
@@ -3620,8 +3670,8 @@ var RunHandler = class {
3620
3670
  const threadId = agent.threadId;
3621
3671
  const executing = this._executingToolCalls.get(agent) ?? /* @__PURE__ */ new Set();
3622
3672
  this._executingToolCalls.set(agent, executing);
3623
- if (toolExecutionMode === "human-in-the-loop" && agent.messages.some((message) => message.role === "assistant" && message.toolCalls?.some((call) => executing.has(JSON.stringify([threadId, call.id])) && !agent.messages.some((result) => result.role === "tool" && result.toolCallId === call.id && !this.isFrontendPlaceholderResult(result))))) return runAgentResult;
3624
- const messagesToProcess = toolExecutionMode === "human-in-the-loop" ? [...agent.messages] : newMessages;
3673
+ if (toolExecutionMode === "restore" && agent.messages.some((message) => message.role === "assistant" && message.toolCalls?.some((call) => executing.has(JSON.stringify([threadId, call.id])) && !agent.messages.some((result) => result.role === "tool" && result.toolCallId === call.id && !this.isFrontendPlaceholderResult(result))))) return runAgentResult;
3674
+ const messagesToProcess = toolExecutionMode === "restore" ? [...agent.messages] : newMessages;
3625
3675
  for (const message of messagesToProcess) if (message.role === "assistant") for (const toolCall of message.toolCalls || []) {
3626
3676
  const tool = this.getTool({
3627
3677
  toolName: toolCall.function.name,
@@ -3637,7 +3687,7 @@ var RunHandler = class {
3637
3687
  return wildcardTool;
3638
3688
  };
3639
3689
  const executableTool = tool ?? getWildcardTool();
3640
- if (toolExecutionMode === "human-in-the-loop" && (executableTool?.type !== "human-in-the-loop" || !executableTool.handler)) continue;
3690
+ if (toolExecutionMode === "restore" && (!executableTool?.handler || executableTool.type !== "human-in-the-loop" && executableTool.reconnectBehavior !== "resume-pending")) continue;
3641
3691
  const executionKey = JSON.stringify([threadId, toolCall.id]);
3642
3692
  if (signal?.aborted || agent.threadId !== threadId || executing.has(executionKey)) continue;
3643
3693
  const existingResultIndex = newMessages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
@@ -3668,14 +3718,14 @@ var RunHandler = class {
3668
3718
  if (signal?.aborted) abortInteraction();
3669
3719
  }
3670
3720
  const executionSignal = interactionController?.signal ?? signal;
3671
- const discardOnAbort = toolExecutionMode === "human-in-the-loop" || !!interactionController;
3721
+ const discardOnAbort = toolExecutionMode === "restore" || !!interactionController;
3672
3722
  try {
3673
3723
  if (tool) {
3674
- if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3724
+ if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId, executionSignal, discardOnAbort, toolExecutionMode === "restore")) needsFollowUp = true;
3675
3725
  } else {
3676
3726
  const fallbackTool = getWildcardTool();
3677
3727
  if (fallbackTool) {
3678
- if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3728
+ if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId, executionSignal, discardOnAbort, toolExecutionMode === "restore")) needsFollowUp = true;
3679
3729
  }
3680
3730
  }
3681
3731
  } finally {
@@ -3709,7 +3759,7 @@ var RunHandler = class {
3709
3759
  * Handles arg parsing, subscriber notifications, handler invocation, result stringification,
3710
3760
  * and error handling.
3711
3761
  */
3712
- async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false }) {
3762
+ async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false, isReplay = false }) {
3713
3763
  let toolCallResult = "";
3714
3764
  let toolCallText = "";
3715
3765
  let errorMessage;
@@ -3745,7 +3795,8 @@ var RunHandler = class {
3745
3795
  const result = await tool.handler(parsedArgs, {
3746
3796
  toolCall,
3747
3797
  agent,
3748
- signal
3798
+ signal,
3799
+ ...isReplay ? { isReplay } : {}
3749
3800
  });
3750
3801
  ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3751
3802
  } catch (error) {
@@ -3783,7 +3834,7 @@ var RunHandler = class {
3783
3834
  /**
3784
3835
  * Execute a specific tool
3785
3836
  */
3786
- async executeSpecificTool(tool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3837
+ async executeSpecificTool(tool, toolCall, message, agent, agentId, signal, discardOnAbort = false, isReplay = false) {
3787
3838
  const threadId = agent.threadId;
3788
3839
  if (tool?.agentId && tool.agentId !== agent.agentId) return false;
3789
3840
  let handlerResult = {
@@ -3801,7 +3852,8 @@ var RunHandler = class {
3801
3852
  toolType: "specific",
3802
3853
  messageId: message.id,
3803
3854
  signal,
3804
- discardOnAbort
3855
+ discardOnAbort,
3856
+ isReplay
3805
3857
  });
3806
3858
  {
3807
3859
  const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
@@ -3825,7 +3877,7 @@ var RunHandler = class {
3825
3877
  * specific tools, so this method keeps its own arg-wrapping logic rather than
3826
3878
  * delegating to `executeToolHandler`.
3827
3879
  */
3828
- async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3880
+ async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId, signal, discardOnAbort = false, isReplay = false) {
3829
3881
  const threadId = agent.threadId;
3830
3882
  if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
3831
3883
  let toolCallResult = "";
@@ -3866,7 +3918,8 @@ var RunHandler = class {
3866
3918
  const result = await wildcardTool.handler(wildcardArgs, {
3867
3919
  toolCall,
3868
3920
  agent,
3869
- signal
3921
+ signal,
3922
+ ...isReplay ? { isReplay } : {}
3870
3923
  });
3871
3924
  ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3872
3925
  } catch (error) {
@@ -4649,9 +4702,12 @@ var TrajectoryConnection = class {
4649
4702
  session.retry = void 0;
4650
4703
  this.connect(session);
4651
4704
  }
4652
- }
4705
+ },
4706
+ linkedThreads: /* @__PURE__ */ new Set(),
4707
+ agentWatches: /* @__PURE__ */ new Map()
4653
4708
  };
4654
4709
  this.session = session;
4710
+ session.runWatch = this.core.subscribe({ onAgentRunStarted: ({ agent }) => this.watchAgent(session, agent) });
4655
4711
  if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4656
4712
  window.addEventListener("offline", session.onOffline);
4657
4713
  window.addEventListener("online", session.onOnline);
@@ -4679,6 +4735,29 @@ var TrajectoryConnection = class {
4679
4735
  if (session?.ready) session.collector?.emit(name, value);
4680
4736
  else if (session?.started) this.addDropped(session, 1, false);
4681
4737
  }
4738
+ watchAgent(session, agent) {
4739
+ if (this.session !== session || session.agentWatches.has(agent)) return;
4740
+ session.agentWatches.set(agent, agent.subscribe({ onRunStartedEvent: ({ event }) => this.linkThread(session, event) }));
4741
+ }
4742
+ /** Sends one `thread.linked` per Thread. A link that cannot be sent waits for the next run. */
4743
+ linkThread(session, { threadId }) {
4744
+ const connection = session.connection;
4745
+ if (!session.ready || !connection || !threadId || session.linkedThreads.has(threadId)) return;
4746
+ session.linkedThreads.add(threadId);
4747
+ const event = {
4748
+ type: "CUSTOM",
4749
+ name: "thread.linked",
4750
+ timestamp: Date.now(),
4751
+ value: { threadId }
4752
+ };
4753
+ try {
4754
+ const { beforeSend } = session.config;
4755
+ const kept = beforeSend === void 0 ? event : beforeSend(event);
4756
+ if (kept !== null) this.send(session, connection, kept);
4757
+ } catch {
4758
+ this.report(session, "INVALID_EVENT");
4759
+ }
4760
+ }
4682
4761
  report(session, code) {
4683
4762
  try {
4684
4763
  session.config.onError?.({
@@ -4694,6 +4773,9 @@ var TrajectoryConnection = class {
4694
4773
  if (this.session !== session) return;
4695
4774
  this.session = void 0;
4696
4775
  clearTimeout(session.retry);
4776
+ session.runWatch?.unsubscribe();
4777
+ for (const watch of session.agentWatches.values()) watch.unsubscribe();
4778
+ session.agentWatches.clear();
4697
4779
  session.collector?.stop();
4698
4780
  session.ready = false;
4699
4781
  if (session.queue.length > 0) this.report(session, "EVENTS_DROPPED");
@@ -4740,6 +4822,7 @@ var TrajectoryConnection = class {
4740
4822
  });
4741
4823
  session.ready = false;
4742
4824
  this.discardQueue(session);
4825
+ session.linkedThreads.clear();
4743
4826
  for (const pending of connection.pending) pending.finish("PERSISTENCE_UNKNOWN");
4744
4827
  if (!this.current(session, connection)) return;
4745
4828
  this.cleanup(session, connection);