@canonmsg/agent-sdk 0.9.2 → 0.10.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.
- package/README.md +3 -3
- package/dist/canon-agent.d.ts +55 -2
- package/dist/canon-agent.js +125 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/realtime.d.ts +7 -1
- package/dist/realtime.js +12 -0
- package/dist/types.d.ts +40 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -250,16 +250,16 @@ The approved response only includes the API key the first time it is delivered.
|
|
|
250
250
|
|
|
251
251
|
## Error Handling
|
|
252
252
|
|
|
253
|
-
The SDK exports `
|
|
253
|
+
The SDK exports `CanonApiError` for typed error handling:
|
|
254
254
|
|
|
255
255
|
```typescript
|
|
256
|
-
import { CanonAgent,
|
|
256
|
+
import { CanonAgent, CanonApiError } from '@canonmsg/agent-sdk';
|
|
257
257
|
|
|
258
258
|
agent.on('message', async ({ messages, reply }) => {
|
|
259
259
|
try {
|
|
260
260
|
await reply('Hello!');
|
|
261
261
|
} catch (err) {
|
|
262
|
-
if (err instanceof
|
|
262
|
+
if (err instanceof CanonApiError) {
|
|
263
263
|
console.error(`API error ${err.status}: ${err.message}`);
|
|
264
264
|
}
|
|
265
265
|
}
|
package/dist/canon-agent.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AddMemberResult, type CanonContact, type ContactCardPayload, type CreateContactRequestResult } from '@canonmsg/core';
|
|
2
|
+
import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Contact-graph operations exposed under `agent.contacts`. Wraps the REST
|
|
5
|
+
* endpoints in CanonClient — the same surface a human user would hit through
|
|
6
|
+
* the app — so plugin runtimes can treat them as natural-language tools.
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentContactsAPI {
|
|
9
|
+
list(): Promise<CanonContact[]>;
|
|
10
|
+
get(contactId: string): Promise<CanonContact | null>;
|
|
11
|
+
remove(contactId: string): Promise<void>;
|
|
12
|
+
request(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* User-level moderation actions exposed under `agent.users`.
|
|
16
|
+
*/
|
|
17
|
+
export interface AgentUsersAPI {
|
|
18
|
+
block(userId: string): Promise<void>;
|
|
19
|
+
unblock(userId: string): Promise<void>;
|
|
20
|
+
}
|
|
2
21
|
export declare class CanonAgent {
|
|
3
22
|
private options;
|
|
4
23
|
private apiClient;
|
|
@@ -10,6 +29,13 @@ export declare class CanonAgent {
|
|
|
10
29
|
private handler;
|
|
11
30
|
private contactRequestHandler;
|
|
12
31
|
private contactApprovedHandler;
|
|
32
|
+
private contactAddedHandler;
|
|
33
|
+
private contactRemovedHandler;
|
|
34
|
+
/** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
|
|
35
|
+
readonly contacts: AgentContactsAPI;
|
|
36
|
+
/** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
|
|
37
|
+
readonly users: AgentUsersAPI;
|
|
38
|
+
private readonly reachOutInFlight;
|
|
13
39
|
private agentId;
|
|
14
40
|
private agentContext;
|
|
15
41
|
private cachedConversationIds;
|
|
@@ -19,6 +45,18 @@ export declare class CanonAgent {
|
|
|
19
45
|
on(event: 'message', handler: MessageHandler): void;
|
|
20
46
|
on(event: 'contactRequest', handler: ContactRequestHandler): void;
|
|
21
47
|
on(event: 'contactApproved', handler: ContactRequestHandler): void;
|
|
48
|
+
on(event: 'contactAdded', handler: ContactAddedHandler): void;
|
|
49
|
+
on(event: 'contactRemoved', handler: ContactRemovedHandler): void;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve admission live for a target user (typically read off a shared
|
|
52
|
+
* contact card) and route into either an immediate message or a contact
|
|
53
|
+
* request. Never reads `card.accessLevel` — that snapshot is stale by the
|
|
54
|
+
* time an LLM acts on it. Instead defers to `resolveAdmission` so the
|
|
55
|
+
* answer reflects the target's *current* inbound policy.
|
|
56
|
+
*/
|
|
57
|
+
reachOut(card: ContactCardPayload, options?: ReachOutOptions): Promise<ReachOutResult>;
|
|
58
|
+
private executeReachOut;
|
|
59
|
+
private openConversationAndMaybeMessage;
|
|
22
60
|
start(): Promise<void>;
|
|
23
61
|
createConversation(options: CreateConversationOptions): Promise<{
|
|
24
62
|
conversationId: string;
|
|
@@ -26,13 +64,28 @@ export declare class CanonAgent {
|
|
|
26
64
|
updateTopic(conversationId: string, topic: string): Promise<void>;
|
|
27
65
|
leaveConversation(conversationId: string): Promise<void>;
|
|
28
66
|
updateConversationName(conversationId: string, name: string): Promise<void>;
|
|
29
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Add a member to a group conversation.
|
|
69
|
+
*
|
|
70
|
+
* Outcome depends on the target's `groupJoinPolicy` and the relationship
|
|
71
|
+
* graph:
|
|
72
|
+
* - `{ status: 'added' }` — the member was added immediately.
|
|
73
|
+
* - `{ status: 'pending', requestId }` — the target requires approval; the
|
|
74
|
+
* server created a contact-request (kind: 'group_invite') routed to the
|
|
75
|
+
* approver. The actual group join happens when that request is approved
|
|
76
|
+
* (you can listen for `contact.approved` SSE events to know when).
|
|
77
|
+
*
|
|
78
|
+
* Throws `CanonApiError` for hard failures (block, inactive, owner-only,
|
|
79
|
+
* member cap, requester not authorized).
|
|
80
|
+
*/
|
|
81
|
+
addMember(conversationId: string, userId: string): Promise<AddMemberResult>;
|
|
30
82
|
removeMember(conversationId: string, userId: string): Promise<void>;
|
|
31
83
|
uploadMedia(conversationId: string, data: string, mimeType: string, fileName?: string): Promise<{
|
|
32
84
|
url: string;
|
|
33
85
|
attachment: import('@canonmsg/core').MediaAttachment;
|
|
34
86
|
}>;
|
|
35
87
|
private handleContactRequestEvent;
|
|
88
|
+
private handleContactGraphEvent;
|
|
36
89
|
stop(): Promise<void>;
|
|
37
90
|
private publishAgentRuntime;
|
|
38
91
|
private startRuntimeHeartbeat;
|
package/dist/canon-agent.js
CHANGED
|
@@ -33,6 +33,13 @@ export class CanonAgent {
|
|
|
33
33
|
handler = null;
|
|
34
34
|
contactRequestHandler = null;
|
|
35
35
|
contactApprovedHandler = null;
|
|
36
|
+
contactAddedHandler = null;
|
|
37
|
+
contactRemovedHandler = null;
|
|
38
|
+
/** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
|
|
39
|
+
contacts;
|
|
40
|
+
/** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
|
|
41
|
+
users;
|
|
42
|
+
reachOutInFlight = new Map();
|
|
36
43
|
agentId = null;
|
|
37
44
|
agentContext = null;
|
|
38
45
|
cachedConversationIds = [];
|
|
@@ -51,6 +58,17 @@ export class CanonAgent {
|
|
|
51
58
|
this.apiClient = new CanonClient(this.options.apiKey, this.options.baseUrl);
|
|
52
59
|
this.authManager = new AuthManager(this.apiClient);
|
|
53
60
|
this.debouncer = new Debouncer(this.options.debounceMs);
|
|
61
|
+
const apiClient = this.apiClient;
|
|
62
|
+
this.contacts = {
|
|
63
|
+
list: () => apiClient.listContacts(),
|
|
64
|
+
get: (contactId) => apiClient.getContact(contactId),
|
|
65
|
+
remove: (contactId) => apiClient.deleteContact(contactId),
|
|
66
|
+
request: (targetUserId, message) => apiClient.createContactRequest(targetUserId, message ?? null),
|
|
67
|
+
};
|
|
68
|
+
this.users = {
|
|
69
|
+
block: (userId) => apiClient.blockUser(userId),
|
|
70
|
+
unblock: (userId) => apiClient.unblockUser(userId),
|
|
71
|
+
};
|
|
54
72
|
if (options.sessions?.enabled) {
|
|
55
73
|
this.sessionManager = new SessionManager({
|
|
56
74
|
contextLimit: options.sessions.contextLimit,
|
|
@@ -68,7 +86,78 @@ export class CanonAgent {
|
|
|
68
86
|
this.contactRequestHandler = handler;
|
|
69
87
|
return;
|
|
70
88
|
}
|
|
71
|
-
|
|
89
|
+
if (event === 'contactApproved') {
|
|
90
|
+
this.contactApprovedHandler = handler;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (event === 'contactAdded') {
|
|
94
|
+
this.contactAddedHandler = handler;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.contactRemovedHandler = handler;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve admission live for a target user (typically read off a shared
|
|
101
|
+
* contact card) and route into either an immediate message or a contact
|
|
102
|
+
* request. Never reads `card.accessLevel` — that snapshot is stale by the
|
|
103
|
+
* time an LLM acts on it. Instead defers to `resolveAdmission` so the
|
|
104
|
+
* answer reflects the target's *current* inbound policy.
|
|
105
|
+
*/
|
|
106
|
+
async reachOut(card, options) {
|
|
107
|
+
const targetUserId = card.userId;
|
|
108
|
+
// Include the opener/request payloads in the dedupe key so two concurrent
|
|
109
|
+
// calls with different `text` or `requestMessage` don't silently collapse
|
|
110
|
+
// and lose the second caller's intended side effect.
|
|
111
|
+
const inFlightKey = `${targetUserId}\u0000${options?.text ?? ''}\u0000${options?.requestMessage ?? ''}`;
|
|
112
|
+
const inFlight = this.reachOutInFlight.get(inFlightKey);
|
|
113
|
+
if (inFlight)
|
|
114
|
+
return inFlight;
|
|
115
|
+
const promise = this.executeReachOut(targetUserId, options).finally(() => {
|
|
116
|
+
this.reachOutInFlight.delete(inFlightKey);
|
|
117
|
+
});
|
|
118
|
+
this.reachOutInFlight.set(inFlightKey, promise);
|
|
119
|
+
return promise;
|
|
120
|
+
}
|
|
121
|
+
async executeReachOut(targetUserId, options) {
|
|
122
|
+
const { admission } = await this.apiClient.resolveAdmission(targetUserId);
|
|
123
|
+
if (admission.state === 'allowed' && admission.canMessage) {
|
|
124
|
+
return this.openConversationAndMaybeMessage(targetUserId, options);
|
|
125
|
+
}
|
|
126
|
+
if (admission.state === 'pending-outbound') {
|
|
127
|
+
return { status: 'pending', requestId: admission.pendingRequestId ?? null };
|
|
128
|
+
}
|
|
129
|
+
if (admission.state === 'request-required' && admission.canRequestContact) {
|
|
130
|
+
const result = await this.apiClient.createContactRequest(targetUserId, options?.requestMessage ?? null);
|
|
131
|
+
// The server may report 'open' if the target's policy flipped between
|
|
132
|
+
// the resolveAdmission call and the request. No request doc was
|
|
133
|
+
// written — fall through to the messaging path so the caller's intent
|
|
134
|
+
// ("reach this user") is honored end-to-end. Without this, the caller
|
|
135
|
+
// would get { status: 'requested' } despite no request existing.
|
|
136
|
+
if (result.status === 'open') {
|
|
137
|
+
return this.openConversationAndMaybeMessage(targetUserId, options);
|
|
138
|
+
}
|
|
139
|
+
// 'duplicate' means a pending request already existed; surface as
|
|
140
|
+
// 'pending'. 'created' is the normal "request just landed" path.
|
|
141
|
+
if (result.status === 'duplicate') {
|
|
142
|
+
return { status: 'pending', requestId: result.requestId };
|
|
143
|
+
}
|
|
144
|
+
return { status: 'requested', requestId: result.requestId };
|
|
145
|
+
}
|
|
146
|
+
if (admission.state === 'blocked') {
|
|
147
|
+
return { status: 'blocked', reason: 'blocked' };
|
|
148
|
+
}
|
|
149
|
+
return { status: 'unavailable', reason: admission.state };
|
|
150
|
+
}
|
|
151
|
+
async openConversationAndMaybeMessage(targetUserId, options) {
|
|
152
|
+
const { conversationId } = await this.apiClient.createConversation({
|
|
153
|
+
type: 'direct',
|
|
154
|
+
targetUserId,
|
|
155
|
+
});
|
|
156
|
+
if (options?.text) {
|
|
157
|
+
const { messageId } = await this.apiClient.sendMessage(conversationId, options.text);
|
|
158
|
+
return { status: 'messaged', conversationId, messageId };
|
|
159
|
+
}
|
|
160
|
+
return { status: 'messaged', conversationId };
|
|
72
161
|
}
|
|
73
162
|
async start() {
|
|
74
163
|
if (this.running)
|
|
@@ -133,6 +222,14 @@ export class CanonAgent {
|
|
|
133
222
|
void this.handleContactRequestEvent(this.contactApprovedHandler, request);
|
|
134
223
|
},
|
|
135
224
|
});
|
|
225
|
+
rtm.setContactGraphHandlers({
|
|
226
|
+
onContactAdded: (payload) => {
|
|
227
|
+
void this.handleContactGraphEvent(this.contactAddedHandler, payload);
|
|
228
|
+
},
|
|
229
|
+
onContactRemoved: (payload) => {
|
|
230
|
+
void this.handleContactGraphEvent(this.contactRemovedHandler, payload);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
136
233
|
rtm.setConnectionHandlers({
|
|
137
234
|
onConnected: () => this.startRuntimeHeartbeat(),
|
|
138
235
|
onDisconnected: () => this.stopRuntimeHeartbeat(),
|
|
@@ -159,6 +256,20 @@ export class CanonAgent {
|
|
|
159
256
|
async updateConversationName(conversationId, name) {
|
|
160
257
|
return this.apiClient.updateConversationName(conversationId, name);
|
|
161
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Add a member to a group conversation.
|
|
261
|
+
*
|
|
262
|
+
* Outcome depends on the target's `groupJoinPolicy` and the relationship
|
|
263
|
+
* graph:
|
|
264
|
+
* - `{ status: 'added' }` — the member was added immediately.
|
|
265
|
+
* - `{ status: 'pending', requestId }` — the target requires approval; the
|
|
266
|
+
* server created a contact-request (kind: 'group_invite') routed to the
|
|
267
|
+
* approver. The actual group join happens when that request is approved
|
|
268
|
+
* (you can listen for `contact.approved` SSE events to know when).
|
|
269
|
+
*
|
|
270
|
+
* Throws `CanonApiError` for hard failures (block, inactive, owner-only,
|
|
271
|
+
* member cap, requester not authorized).
|
|
272
|
+
*/
|
|
162
273
|
async addMember(conversationId, userId) {
|
|
163
274
|
return this.apiClient.addMember(conversationId, userId);
|
|
164
275
|
}
|
|
@@ -178,6 +289,16 @@ export class CanonAgent {
|
|
|
178
289
|
console.error('[canon-sdk] Contact-request handler failed:', error instanceof Error ? error.message : error);
|
|
179
290
|
}
|
|
180
291
|
}
|
|
292
|
+
async handleContactGraphEvent(handler, payload) {
|
|
293
|
+
if (!handler)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
await handler(payload);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
console.error('[canon-sdk] Contact-graph handler failed:', error instanceof Error ? error.message : error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
181
302
|
async stop() {
|
|
182
303
|
if (!this.running)
|
|
183
304
|
return;
|
|
@@ -385,7 +506,9 @@ export class CanonAgent {
|
|
|
385
506
|
agentId: this.agentId,
|
|
386
507
|
ownerId: '',
|
|
387
508
|
ownerName: '',
|
|
388
|
-
|
|
509
|
+
discoverable: false,
|
|
510
|
+
inboundPolicy: 'approval-required',
|
|
511
|
+
groupJoinPolicy: 'approval-required',
|
|
389
512
|
};
|
|
390
513
|
// Build context methods bound to this conversation
|
|
391
514
|
const deleteMessage = (messageId) => this.apiClient.deleteMessage(conversationId, messageId);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { CanonAgent } from './canon-agent.js';
|
|
2
|
-
export {
|
|
2
|
+
export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
|
|
3
|
+
export { CanonApiError, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, } from '@canonmsg/core';
|
|
4
|
+
export type { CanonContact, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, } from '@canonmsg/core';
|
|
3
5
|
export { SessionManager } from './session-manager.js';
|
|
4
6
|
export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
5
7
|
export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
|
|
6
8
|
export type { SessionConfig, Session } from './session-manager.js';
|
|
7
9
|
export type { AgentContext, CanonContactRequest, CanonMessage, CanonConversation, CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, SendMessageOptions, CreateConversationOptions, UpdateWorkSessionConversationOptions, } from '@canonmsg/core';
|
|
8
|
-
export type { SDKMessage, SDKConversation, CanonAgentOptions, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
|
10
|
+
export type { SDKMessage, SDKConversation, 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
|
-
export { CanonApiError } from '@canonmsg/core';
|
|
2
|
+
export { CanonApiError, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, } from '@canonmsg/core';
|
|
3
3
|
export { SessionManager } from './session-manager.js';
|
|
4
4
|
export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentContext, type CanonClient, type ContactApprovedPayload, type ContactRequestPayload } from '@canonmsg/core';
|
|
1
|
+
import { type AgentContext, type CanonClient, type ContactAddedPayload, type ContactApprovedPayload, type ContactRemovedPayload, type ContactRequestPayload } from '@canonmsg/core';
|
|
2
2
|
import { Debouncer } from './debouncer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Wraps @canonmsg/core's CanonStream with SDK-specific features:
|
|
@@ -13,6 +13,8 @@ export declare class RealtimeManager {
|
|
|
13
13
|
private onAgentContext;
|
|
14
14
|
private onContactRequest;
|
|
15
15
|
private onContactApproved;
|
|
16
|
+
private onContactAdded;
|
|
17
|
+
private onContactRemoved;
|
|
16
18
|
private onConnected;
|
|
17
19
|
private onDisconnected;
|
|
18
20
|
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, apiClient?: CanonClient);
|
|
@@ -21,6 +23,10 @@ export declare class RealtimeManager {
|
|
|
21
23
|
onContactRequest?: (payload: ContactRequestPayload) => void;
|
|
22
24
|
onContactApproved?: (payload: ContactApprovedPayload) => void;
|
|
23
25
|
}): void;
|
|
26
|
+
setContactGraphHandlers(handlers: {
|
|
27
|
+
onContactAdded?: (payload: ContactAddedPayload) => void;
|
|
28
|
+
onContactRemoved?: (payload: ContactRemovedPayload) => void;
|
|
29
|
+
}): void;
|
|
24
30
|
setConnectionHandlers(handlers: {
|
|
25
31
|
onConnected?: () => void;
|
|
26
32
|
onDisconnected?: () => void;
|
package/dist/realtime.js
CHANGED
|
@@ -12,6 +12,8 @@ export class RealtimeManager {
|
|
|
12
12
|
onAgentContext = null;
|
|
13
13
|
onContactRequest = null;
|
|
14
14
|
onContactApproved = null;
|
|
15
|
+
onContactAdded = null;
|
|
16
|
+
onContactRemoved = null;
|
|
15
17
|
onConnected = null;
|
|
16
18
|
onDisconnected = null;
|
|
17
19
|
constructor(apiKey, debouncer, agentId, streamUrl, apiClient) {
|
|
@@ -59,6 +61,12 @@ export class RealtimeManager {
|
|
|
59
61
|
onContactApproved: (payload) => {
|
|
60
62
|
this.onContactApproved?.(payload);
|
|
61
63
|
},
|
|
64
|
+
onContactAdded: (payload) => {
|
|
65
|
+
this.onContactAdded?.(payload);
|
|
66
|
+
},
|
|
67
|
+
onContactRemoved: (payload) => {
|
|
68
|
+
this.onContactRemoved?.(payload);
|
|
69
|
+
},
|
|
62
70
|
onConnected: () => {
|
|
63
71
|
// Reset backoff is handled internally by CanonStream
|
|
64
72
|
this.onConnected?.();
|
|
@@ -79,6 +87,10 @@ export class RealtimeManager {
|
|
|
79
87
|
this.onContactRequest = handlers.onContactRequest ?? null;
|
|
80
88
|
this.onContactApproved = handlers.onContactApproved ?? null;
|
|
81
89
|
}
|
|
90
|
+
setContactGraphHandlers(handlers) {
|
|
91
|
+
this.onContactAdded = handlers.onContactAdded ?? null;
|
|
92
|
+
this.onContactRemoved = handlers.onContactRemoved ?? null;
|
|
93
|
+
}
|
|
82
94
|
setConnectionHandlers(handlers) {
|
|
83
95
|
this.onConnected = handlers.onConnected ?? null;
|
|
84
96
|
this.onDisconnected = handlers.onDisconnected ?? null;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { AgentClientType, CanonRuntimeDescriptor, CanonMessage, CanonConversation, CanonContactRequest, AgentContext, CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, SendMessageOptions, CreateConversationOptions, TurnLifecycleState, UpdateWorkSessionConversationOptions, } from '@canonmsg/core';
|
|
2
|
-
import type { CanonMessage, CanonConversation, CreateWorkSessionOptions, SendMessageOptions, UpdateWorkSessionConversationOptions } from '@canonmsg/core';
|
|
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, CreateWorkSessionOptions, SendMessageOptions, UpdateWorkSessionConversationOptions } from '@canonmsg/core';
|
|
3
3
|
import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
|
|
4
4
|
export type SDKMessage = CanonMessage;
|
|
5
5
|
export type SDKConversation = CanonConversation;
|
|
@@ -56,8 +56,14 @@ export interface MessageHandlerContext {
|
|
|
56
56
|
leave: () => Promise<void>;
|
|
57
57
|
/** Toggle emoji reaction on a message */
|
|
58
58
|
react: (messageId: string, emoji: string) => Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Add a member to this conversation (requires owner/admin role).
|
|
61
|
+
* Returns `{ status: 'added' }` on immediate add, or
|
|
62
|
+
* `{ status: 'pending', requestId }` if the target requires approval —
|
|
63
|
+
* a contact-request (kind: 'group_invite') is created and the join
|
|
64
|
+
* lands when the approver accepts.
|
|
65
|
+
*/
|
|
66
|
+
addMember: (userId: string) => Promise<AddMemberResult>;
|
|
61
67
|
/** Remove a member from this conversation (requires owner/admin role) */
|
|
62
68
|
removeMember: (userId: string) => Promise<void>;
|
|
63
69
|
/** Create a Canon work session rooted in this conversation. */
|
|
@@ -126,3 +132,33 @@ export interface CanonAgentOptions {
|
|
|
126
132
|
sessionState?: boolean;
|
|
127
133
|
}
|
|
128
134
|
export type ContactRequestHandler = (request: import('@canonmsg/core').CanonContactRequest) => void | Promise<void>;
|
|
135
|
+
export type ContactAddedHandler = (contact: import('@canonmsg/core').ContactAddedPayload) => void | Promise<void>;
|
|
136
|
+
export type ContactRemovedHandler = (payload: import('@canonmsg/core').ContactRemovedPayload) => void | Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Result of `agent.reachOut(card)` — describes which side-effect ran so the
|
|
139
|
+
* caller can decide what to tell the LLM. `messaged` means the agent opened
|
|
140
|
+
* (or sent into) a direct conversation; `requested` means the target's
|
|
141
|
+
* inbound policy required a contact request, which has been created;
|
|
142
|
+
* `pending` means a prior outbound request is still awaiting approval; and
|
|
143
|
+
* `blocked` / `unavailable` describe terminal states with `reason` set.
|
|
144
|
+
*/
|
|
145
|
+
export type ReachOutResult = {
|
|
146
|
+
status: 'messaged';
|
|
147
|
+
conversationId: string;
|
|
148
|
+
messageId?: string;
|
|
149
|
+
} | {
|
|
150
|
+
status: 'requested';
|
|
151
|
+
requestId: string | null;
|
|
152
|
+
} | {
|
|
153
|
+
status: 'pending';
|
|
154
|
+
requestId: string | null;
|
|
155
|
+
} | {
|
|
156
|
+
status: 'blocked' | 'unavailable';
|
|
157
|
+
reason: string;
|
|
158
|
+
};
|
|
159
|
+
export interface ReachOutOptions {
|
|
160
|
+
/** Optional first message to send when admission is `allowed`. */
|
|
161
|
+
text?: string;
|
|
162
|
+
/** Optional message to attach to the contact request when admission is `request-required`. */
|
|
163
|
+
requestMessage?: string;
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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.
|
|
31
|
+
"@canonmsg/core": "^0.12.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|