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