@canonmsg/agent-sdk 1.1.1 → 1.1.3

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.
@@ -1,4 +1,4 @@
1
- import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, rtdbRead, rtdbWrite, mergeWorkSessionContexts, normalizeTurnMetadata, reachOutToCanonContact, } from '@canonmsg/core';
1
+ import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, } from '@canonmsg/core';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { AuthManager } from './auth.js';
4
4
  import { Debouncer } from './debouncer.js';
@@ -150,9 +150,12 @@ export class CanonAgent {
150
150
  async reachOut(card, options) {
151
151
  const targetUserId = card.userId;
152
152
  // Include the opener/request payloads in the dedupe key so two concurrent
153
- // calls with different `text` or `requestMessage` don't silently collapse
153
+ // calls with different `text`, `requestMessage`, or setup choices don't silently collapse
154
154
  // and lose the second caller's intended side effect.
155
- const inFlightKey = `${targetUserId}\u0000${options?.text ?? ''}\u0000${options?.requestMessage ?? ''}`;
155
+ const contextualKey = options?.selfContext
156
+ ? `${options.sourceConversationId ?? ''}\u0000${options.selfContext.type}\u0000${options.selfContext.context}`
157
+ : '';
158
+ const inFlightKey = `${targetUserId}\u0000${options?.text ?? ''}\u0000${options?.requestMessage ?? ''}\u0000${JSON.stringify(options?.sessionConfig ?? null)}\u0000${contextualKey}`;
156
159
  const inFlight = this.reachOutInFlight.get(inFlightKey);
157
160
  if (inFlight)
158
161
  return inFlight;
@@ -163,10 +166,34 @@ export class CanonAgent {
163
166
  return promise;
164
167
  }
165
168
  async executeReachOut(targetUserId, options) {
169
+ if (options?.selfContext) {
170
+ if (!options.sourceConversationId) {
171
+ throw new Error('sourceConversationId is required for contextual reachOut');
172
+ }
173
+ if (!options.text) {
174
+ throw new Error('text is required for contextual reachOut');
175
+ }
176
+ const result = await this.apiClient.sendContextualMessage({
177
+ sourceConversationId: options.sourceConversationId,
178
+ targetUserId,
179
+ text: options.text,
180
+ selfContext: options.selfContext,
181
+ requestMessage: options.requestMessage ?? null,
182
+ sessionConfig: options.sessionConfig ?? null,
183
+ });
184
+ return result.status === 'messaged'
185
+ ? {
186
+ status: 'messaged',
187
+ conversationId: result.conversationId,
188
+ messageId: result.messageId,
189
+ }
190
+ : result;
191
+ }
166
192
  return reachOutToCanonContact(this.apiClient, {
167
193
  targetUserId,
168
194
  text: options?.text ?? null,
169
195
  requestMessage: options?.requestMessage ?? null,
196
+ sessionConfig: options?.sessionConfig ?? null,
170
197
  });
171
198
  }
172
199
  async start() {
@@ -633,6 +660,9 @@ export class CanonAgent {
633
660
  catch { }
634
661
  const result = await this.apiClient.sendMessage(conversationId, text, {
635
662
  ...(options ?? {}),
663
+ ...(options?.selfContextId === undefined && activeSelfContextId
664
+ ? { selfContextId: activeSelfContextId }
665
+ : {}),
636
666
  metadata: {
637
667
  ...(options?.metadata ?? {}),
638
668
  turnId,
@@ -671,11 +701,8 @@ export class CanonAgent {
671
701
  m.isOwner = m.senderId === ownerId;
672
702
  }
673
703
  }
674
- const explicitWorkSession = messages.find((message) => message.workSession)?.workSession
675
- ?? history.find((message) => message.workSession)?.workSession
676
- ?? null;
677
- const activeWorkSessions = mergeWorkSessionContexts(explicitWorkSession, page.workSessions ?? []);
678
- const workSession = explicitWorkSession;
704
+ const selfContexts = page.selfContexts ?? [];
705
+ const activeSelfContextId = selfContexts[0]?.id;
679
706
  // Build agent context (fallback to minimal if not yet received)
680
707
  const agent = this.agentContext ?? {
681
708
  agentId: this.agentId,
@@ -692,32 +719,21 @@ export class CanonAgent {
692
719
  const react = (messageId, emoji) => this.apiClient.react(conversationId, messageId, emoji);
693
720
  const addMember = (userId) => this.apiClient.addMember(conversationId, userId);
694
721
  const removeMember = (userId) => this.apiClient.removeMember(conversationId, userId);
695
- const createWorkSession = (options) => this.apiClient.createWorkSession({
696
- conversationId,
697
- ...(options ?? {}),
698
- });
699
- const getWorkSession = (workSessionId, targetConversationId = conversationId) => this.apiClient.getWorkSession(workSessionId, targetConversationId);
700
- const updateWorkSessionContext = (workSessionId, options) => this.apiClient.upsertWorkSessionConversation(workSessionId, conversationId, options);
701
- const sendLinkedMessage = (targetConversationId, text, options) => {
702
- if (!options?.workSessionId && !options?.createWorkSession) {
703
- throw new Error('sendLinkedMessage requires workSessionId or createWorkSession');
704
- }
705
- return this.apiClient.sendLinkedMessage({
706
- sourceConversationId: conversationId,
707
- targetConversationId,
708
- text,
709
- ...(options ?? {}),
710
- messageOptions: {
711
- ...(options?.messageOptions ?? {}),
712
- metadata: {
713
- ...(options?.messageOptions?.metadata ?? {}),
714
- turnId,
715
- turnSemantics: 'turn_complete',
716
- turnComplete: true,
717
- },
722
+ const sendContextualMessage = (target, text, options) => this.apiClient.sendContextualMessage({
723
+ sourceConversationId: conversationId,
724
+ ...target,
725
+ text,
726
+ ...options,
727
+ messageOptions: {
728
+ ...(options.messageOptions ?? {}),
729
+ metadata: {
730
+ ...(options.messageOptions?.metadata ?? {}),
731
+ turnId,
732
+ turnSemantics: 'turn_complete',
733
+ turnComplete: true,
718
734
  },
719
- });
720
- };
735
+ },
736
+ });
721
737
  const uploadFile = (filePath, options) => uploadMediaFile(this.apiClient, conversationId, filePath, options);
722
738
  const replyWithFile = async (filePath, text = '', options) => {
723
739
  try {
@@ -732,13 +748,15 @@ export class CanonAgent {
732
748
  ? { replyToPosition: options.replyToPosition }
733
749
  : {}),
734
750
  ...(options?.mentions ? { mentions: options.mentions } : {}),
751
+ ...(options?.selfContextId === undefined && activeSelfContextId
752
+ ? { selfContextId: activeSelfContextId }
753
+ : {}),
735
754
  metadata: {
736
755
  ...(options?.metadata ?? {}),
737
756
  turnId,
738
757
  turnSemantics: 'turn_complete',
739
758
  turnComplete: true,
740
759
  },
741
- ...(options?.workSessionId ? { workSessionId: options.workSessionId } : {}),
742
760
  contentType: uploaded.attachment.kind,
743
761
  attachments: [uploaded.attachment],
744
762
  });
@@ -766,13 +784,9 @@ export class CanonAgent {
766
784
  react,
767
785
  addMember,
768
786
  removeMember,
769
- createWorkSession,
770
- getWorkSession,
771
- updateWorkSessionContext,
772
- sendLinkedMessage,
787
+ sendContextualMessage,
773
788
  agent,
774
- workSession,
775
- activeWorkSessions,
789
+ selfContexts,
776
790
  abortSignal: abortController.signal,
777
791
  media: {
778
792
  materialize: (message = hydratedMessages[hydratedMessages.length - 1], options) => {
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@ export { SessionManager } from './session-manager.js';
6
6
  export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
7
7
  export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
8
8
  export type { SessionConfig, Session } from './session-manager.js';
9
- export type { AgentContext, CanonContactRequest, CanonMessage, CanonConversation, CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, SendMessageOptions, CreateConversationOptions, UpdateWorkSessionConversationOptions, } from '@canonmsg/core';
9
+ export type { AgentContext, CanonContactRequest, CanonMessage, CanonConversation, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
10
10
  export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AddMemberResult, AgentClientType, CanonRuntimeDescriptor, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, SendMessageOptions, CreateConversationOptions, TurnLifecycleState, UpdateWorkSessionConversationOptions, } from '@canonmsg/core';
2
- import type { AddMemberResult, CanonMessage, CanonConversation, CanonRuntimeActionDispatch, CreateWorkSessionOptions, SendMessageOptions, UpdateWorkSessionConversationOptions } from '@canonmsg/core';
1
+ export type { AddMemberResult, AgentClientType, CanonRuntimeDescriptor, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, } from '@canonmsg/core';
2
+ import type { AddMemberResult, CanonMessage, CanonConversation, CanonRuntimeActionDispatch, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
3
3
  import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
4
4
  export interface ProgressMessageOptions extends SendMessageOptions {
5
5
  /**
@@ -61,20 +61,16 @@ export interface MessageHandlerContext {
61
61
  addMember: (userId: string) => Promise<AddMemberResult>;
62
62
  /** Remove a member from this conversation (requires owner/admin role) */
63
63
  removeMember: (userId: string) => Promise<void>;
64
- /** Create a Canon work session rooted in this conversation. */
65
- createWorkSession: (options?: Omit<CreateWorkSessionOptions, 'conversationId'>) => Promise<import('@canonmsg/core').CanonResolvedWorkSession>;
66
- /** Load this conversation's scoped view of a Canon work session. */
67
- getWorkSession: (workSessionId: string, conversationId?: string) => Promise<import('@canonmsg/core').CanonResolvedWorkSession>;
68
- /** Update or attach this conversation's scoped work-session context. */
69
- updateWorkSessionContext: (workSessionId: string, options?: UpdateWorkSessionConversationOptions) => Promise<import('@canonmsg/core').CanonResolvedWorkSession>;
70
- /** Send into another conversation under an existing or lazily created Canon work session. */
71
- sendLinkedMessage: (targetConversationId: string, text: string, options?: Omit<import('@canonmsg/core').SendLinkedMessageOptions, 'sourceConversationId' | 'targetConversationId' | 'text'>) => Promise<import('@canonmsg/core').SendLinkedMessageResult>;
64
+ /** Send into another Canon conversation with private cross-session self-context. */
65
+ sendContextualMessage: (target: {
66
+ targetConversationId: string;
67
+ } | {
68
+ targetUserId: string;
69
+ }, text: string, options: Omit<import('@canonmsg/core').SendContextualMessageOptions, 'sourceConversationId' | 'targetConversationId' | 'targetUserId' | 'text'>) => Promise<import('@canonmsg/core').SendContextualMessageResult>;
72
70
  /** Trusted agent identity & access context */
73
71
  agent: import('@canonmsg/core').AgentContext;
74
- /** Canon-provided shared task context for this turn, when attached to inbound messages. */
75
- workSession?: import('@canonmsg/core').CanonWorkSessionContext | null;
76
- /** All active Canon work sessions currently linked to this conversation. */
77
- activeWorkSessions?: import('@canonmsg/core').CanonWorkSessionContext[];
72
+ /** Canon-provided private context explaining this agent's cross-session actions. */
73
+ selfContexts?: import('@canonmsg/core').CanonSelfContext[];
78
74
  /** Canon-managed local media access for the current conversation. */
79
75
  media: {
80
76
  materialize: (message?: CanonMessage, options?: Omit<MaterializeMediaOptions, 'agentId' | 'conversationId' | 'messageId'>) => Promise<MaterializedCanonAttachment[]>;
@@ -165,6 +161,9 @@ export type ReachOutResult = {
165
161
  } | {
166
162
  status: 'pending';
167
163
  requestId: string | null;
164
+ } | {
165
+ status: 'setup_required';
166
+ reason: string;
168
167
  } | {
169
168
  status: 'blocked' | 'unavailable';
170
169
  reason: string;
@@ -174,4 +173,10 @@ export interface ReachOutOptions {
174
173
  text?: string;
175
174
  /** Optional contact-request note. Defaults to `text` when admission is `request-required`. */
176
175
  requestMessage?: string;
176
+ /** Explicit session setup to use when the contact-card target is an agent. */
177
+ sessionConfig?: SessionConfig | null;
178
+ /** Source conversation for contextual cross-session reach-outs. */
179
+ sourceConversationId?: string;
180
+ /** Private context for the agent when this reach-out sends a cross-session message. */
181
+ selfContext?: SendContextualSelfContextInput;
177
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@canonmsg/core": "^0.15.3"
31
+ "@canonmsg/core": "^0.15.5"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"