@canonmsg/agent-sdk 1.1.2 → 1.1.4
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/canon-agent.js +57 -44
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/media.d.ts +5 -0
- package/dist/media.js +29 -3
- package/dist/types.d.ts +14 -14
- package/package.json +2 -2
package/dist/canon-agent.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, rtdbRead, rtdbWrite,
|
|
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';
|
|
5
|
-
import { materializeMessageMedia, uploadMediaFile, } from './media.js';
|
|
5
|
+
import { materializeMessageMedia, sendMediaFileMessage, uploadMediaFile, } from './media.js';
|
|
6
6
|
import { SessionManager } from './session-manager.js';
|
|
7
7
|
const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
|
|
8
8
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
@@ -152,7 +152,10 @@ export class CanonAgent {
|
|
|
152
152
|
// Include the opener/request payloads in the dedupe key so two concurrent
|
|
153
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
|
|
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,6 +166,29 @@ 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,
|
|
@@ -634,6 +660,9 @@ export class CanonAgent {
|
|
|
634
660
|
catch { }
|
|
635
661
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
636
662
|
...(options ?? {}),
|
|
663
|
+
...(options?.selfContextId === undefined && activeSelfContextId
|
|
664
|
+
? { selfContextId: activeSelfContextId }
|
|
665
|
+
: {}),
|
|
637
666
|
metadata: {
|
|
638
667
|
...(options?.metadata ?? {}),
|
|
639
668
|
turnId,
|
|
@@ -672,11 +701,8 @@ export class CanonAgent {
|
|
|
672
701
|
m.isOwner = m.senderId === ownerId;
|
|
673
702
|
}
|
|
674
703
|
}
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
?? null;
|
|
678
|
-
const activeWorkSessions = mergeWorkSessionContexts(explicitWorkSession, page.workSessions ?? []);
|
|
679
|
-
const workSession = explicitWorkSession;
|
|
704
|
+
const selfContexts = page.selfContexts ?? [];
|
|
705
|
+
const activeSelfContextId = selfContexts[0]?.id;
|
|
680
706
|
// Build agent context (fallback to minimal if not yet received)
|
|
681
707
|
const agent = this.agentContext ?? {
|
|
682
708
|
agentId: this.agentId,
|
|
@@ -693,32 +719,21 @@ export class CanonAgent {
|
|
|
693
719
|
const react = (messageId, emoji) => this.apiClient.react(conversationId, messageId, emoji);
|
|
694
720
|
const addMember = (userId) => this.apiClient.addMember(conversationId, userId);
|
|
695
721
|
const removeMember = (userId) => this.apiClient.removeMember(conversationId, userId);
|
|
696
|
-
const
|
|
697
|
-
conversationId,
|
|
698
|
-
...
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
targetConversationId,
|
|
709
|
-
text,
|
|
710
|
-
...(options ?? {}),
|
|
711
|
-
messageOptions: {
|
|
712
|
-
...(options?.messageOptions ?? {}),
|
|
713
|
-
metadata: {
|
|
714
|
-
...(options?.messageOptions?.metadata ?? {}),
|
|
715
|
-
turnId,
|
|
716
|
-
turnSemantics: 'turn_complete',
|
|
717
|
-
turnComplete: true,
|
|
718
|
-
},
|
|
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,
|
|
719
734
|
},
|
|
720
|
-
}
|
|
721
|
-
};
|
|
735
|
+
},
|
|
736
|
+
});
|
|
722
737
|
const uploadFile = (filePath, options) => uploadMediaFile(this.apiClient, conversationId, filePath, options);
|
|
723
738
|
const replyWithFile = async (filePath, text = '', options) => {
|
|
724
739
|
try {
|
|
@@ -726,22 +741,24 @@ export class CanonAgent {
|
|
|
726
741
|
}
|
|
727
742
|
catch { }
|
|
728
743
|
try {
|
|
729
|
-
const
|
|
730
|
-
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
744
|
+
const result = await sendMediaFileMessage(this.apiClient, conversationId, filePath, text, {
|
|
731
745
|
...(options?.replyTo ? { replyTo: options.replyTo } : {}),
|
|
732
746
|
...(options?.replyToPosition != null
|
|
733
747
|
? { replyToPosition: options.replyToPosition }
|
|
734
748
|
: {}),
|
|
735
749
|
...(options?.mentions ? { mentions: options.mentions } : {}),
|
|
750
|
+
...(options?.selfContextId === undefined && activeSelfContextId
|
|
751
|
+
? { selfContextId: activeSelfContextId }
|
|
752
|
+
: {}),
|
|
736
753
|
metadata: {
|
|
737
754
|
...(options?.metadata ?? {}),
|
|
738
755
|
turnId,
|
|
739
756
|
turnSemantics: 'turn_complete',
|
|
740
757
|
turnComplete: true,
|
|
741
758
|
},
|
|
742
|
-
...(options?.
|
|
743
|
-
|
|
744
|
-
|
|
759
|
+
...(options?.fileName ? { fileName: options.fileName } : {}),
|
|
760
|
+
...(options?.mimeType ? { mimeType: options.mimeType } : {}),
|
|
761
|
+
...(options?.durationMs != null ? { durationMs: options.durationMs } : {}),
|
|
745
762
|
});
|
|
746
763
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
747
764
|
return result;
|
|
@@ -767,13 +784,9 @@ export class CanonAgent {
|
|
|
767
784
|
react,
|
|
768
785
|
addMember,
|
|
769
786
|
removeMember,
|
|
770
|
-
|
|
771
|
-
getWorkSession,
|
|
772
|
-
updateWorkSessionContext,
|
|
773
|
-
sendLinkedMessage,
|
|
787
|
+
sendContextualMessage,
|
|
774
788
|
agent,
|
|
775
|
-
|
|
776
|
-
activeWorkSessions,
|
|
789
|
+
selfContexts,
|
|
777
790
|
abortSignal: abortController.signal,
|
|
778
791
|
media: {
|
|
779
792
|
materialize: (message = hydratedMessages[hydratedMessages.length - 1], options) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
|
|
|
3
3
|
export { CanonApiError, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, } from '@canonmsg/core';
|
|
4
4
|
export type { CanonContact, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, } from '@canonmsg/core';
|
|
5
5
|
export { SessionManager } from './session-manager.js';
|
|
6
|
-
export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
6
|
+
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, sendMediaFileMessage, 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,
|
|
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/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CanonAgent } from './canon-agent.js';
|
|
2
2
|
export { CanonApiError, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, } from '@canonmsg/core';
|
|
3
3
|
export { SessionManager } from './session-manager.js';
|
|
4
|
-
export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
4
|
+
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
package/dist/media.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface MaterializeMediaOptions {
|
|
|
10
10
|
export interface UploadMediaFileOptions {
|
|
11
11
|
fileName?: string;
|
|
12
12
|
mimeType?: string;
|
|
13
|
+
durationMs?: number;
|
|
13
14
|
}
|
|
14
15
|
export interface ReplyWithFileOptions extends Omit<SendMessageOptions, 'attachments' | 'contentType'>, UploadMediaFileOptions {
|
|
15
16
|
}
|
|
@@ -39,6 +40,7 @@ export interface AnthropicImageBlock {
|
|
|
39
40
|
data: string;
|
|
40
41
|
};
|
|
41
42
|
}
|
|
43
|
+
export declare const DEFAULT_MEDIA_CACHE_DIR: string;
|
|
42
44
|
export declare function getMessageAttachments(message: Pick<CanonMessage, 'attachments'>): MediaAttachment[];
|
|
43
45
|
export declare function materializeAttachment(attachment: MediaAttachment, options: MaterializeMediaOptions & {
|
|
44
46
|
index?: number;
|
|
@@ -49,6 +51,9 @@ export declare function uploadMediaFile(client: CanonClient, conversationId: str
|
|
|
49
51
|
url: string;
|
|
50
52
|
attachment: MediaAttachment;
|
|
51
53
|
}>;
|
|
54
|
+
export declare function sendMediaFileMessage(client: CanonClient, conversationId: string, filePath: string, text?: string, options?: ReplyWithFileOptions): Promise<{
|
|
55
|
+
messageId: string;
|
|
56
|
+
}>;
|
|
52
57
|
/**
|
|
53
58
|
* Resolve the effective MIME type of a materialized attachment, falling back
|
|
54
59
|
* to filename/URL extensions when the server didn't tell us explicitly.
|
package/dist/media.js
CHANGED
|
@@ -7,7 +7,7 @@ const ANTHROPIC_IMAGE_MIME_TYPES = new Set([
|
|
|
7
7
|
'image/gif',
|
|
8
8
|
'image/webp',
|
|
9
9
|
]);
|
|
10
|
-
const DEFAULT_MEDIA_CACHE_DIR = join(CANON_DIR, 'media-cache');
|
|
10
|
+
export const DEFAULT_MEDIA_CACHE_DIR = join(CANON_DIR, 'media-cache');
|
|
11
11
|
const EXTENSION_BY_MIME = {
|
|
12
12
|
'application/json': 'json',
|
|
13
13
|
'application/pdf': 'pdf',
|
|
@@ -117,7 +117,7 @@ export async function materializeAttachment(attachment, options) {
|
|
|
117
117
|
}
|
|
118
118
|
responseMimeType = response.headers.get('content-type');
|
|
119
119
|
const body = Buffer.from(await response.arrayBuffer());
|
|
120
|
-
await writeFile(path, body);
|
|
120
|
+
await writeFile(path, body, { mode: 0o644 });
|
|
121
121
|
}
|
|
122
122
|
return {
|
|
123
123
|
...attachment,
|
|
@@ -150,7 +150,33 @@ export async function uploadMediaFile(client, conversationId, filePath, options)
|
|
|
150
150
|
const buffer = await readFile(filePath);
|
|
151
151
|
const mimeType = inferUploadMimeType(filePath, options?.mimeType);
|
|
152
152
|
const fileName = options?.fileName ?? basename(filePath);
|
|
153
|
-
|
|
153
|
+
const uploaded = await client.uploadMedia(conversationId, buffer.toString('base64'), mimeType, fileName);
|
|
154
|
+
if (uploaded.attachment.kind === 'audio'
|
|
155
|
+
&& typeof options?.durationMs === 'number'
|
|
156
|
+
&& Number.isFinite(options.durationMs)
|
|
157
|
+
&& options.durationMs > 0) {
|
|
158
|
+
return {
|
|
159
|
+
...uploaded,
|
|
160
|
+
attachment: {
|
|
161
|
+
...uploaded.attachment,
|
|
162
|
+
durationMs: Math.round(options.durationMs),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return uploaded;
|
|
167
|
+
}
|
|
168
|
+
export async function sendMediaFileMessage(client, conversationId, filePath, text = '', options) {
|
|
169
|
+
const { fileName, mimeType, durationMs, ...sendOptions } = options ?? {};
|
|
170
|
+
const uploaded = await uploadMediaFile(client, conversationId, filePath, {
|
|
171
|
+
...(fileName ? { fileName } : {}),
|
|
172
|
+
...(mimeType ? { mimeType } : {}),
|
|
173
|
+
...(durationMs != null ? { durationMs } : {}),
|
|
174
|
+
});
|
|
175
|
+
return client.sendMessage(conversationId, text, {
|
|
176
|
+
...sendOptions,
|
|
177
|
+
contentType: uploaded.attachment.kind,
|
|
178
|
+
attachments: [uploaded.attachment],
|
|
179
|
+
});
|
|
154
180
|
}
|
|
155
181
|
/**
|
|
156
182
|
* Resolve the effective MIME type of a materialized attachment, falling back
|
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,
|
|
2
|
-
import type { AddMemberResult, CanonMessage, CanonConversation, CanonRuntimeActionDispatch,
|
|
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
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
75
|
-
|
|
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[]>;
|
|
@@ -179,4 +175,8 @@ export interface ReachOutOptions {
|
|
|
179
175
|
requestMessage?: string;
|
|
180
176
|
/** Explicit session setup to use when the contact-card target is an agent. */
|
|
181
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;
|
|
182
182
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
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.
|
|
31
|
+
"@canonmsg/core": "^0.15.5"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|