@canonmsg/agent-sdk 1.3.1 → 1.4.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/canon-agent.d.ts +9 -1
- package/dist/canon-agent.js +153 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/media.d.ts +6 -1
- package/dist/media.js +26 -1
- package/dist/types.d.ts +4 -2
- package/package.json +2 -2
package/dist/canon-agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AddMemberResult, type CanonContact, type CanonRuntimePrimitiveId, type ContactCardPayload, type CreateContactRequestResult } from '@canonmsg/core';
|
|
1
|
+
import { type AddMemberResult, type CanonContact, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult } from '@canonmsg/core';
|
|
2
2
|
import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Contact-graph operations exposed under `agent.contacts`. Wraps the REST
|
|
@@ -49,6 +49,7 @@ export declare class CanonAgent {
|
|
|
49
49
|
private readonly lastSeenSignal;
|
|
50
50
|
private readonly primitiveRequestDedupe;
|
|
51
51
|
private readonly activeAbortControllers;
|
|
52
|
+
private readonly activeTurns;
|
|
52
53
|
private readonly conversationMemberIds;
|
|
53
54
|
private readonly pendingMembershipChanges;
|
|
54
55
|
constructor(options: CanonAgentOptions);
|
|
@@ -61,6 +62,10 @@ export declare class CanonAgent {
|
|
|
61
62
|
on(event: 'stopAndDrop', handler: RuntimeSignalHandler): void;
|
|
62
63
|
on(event: 'newSession', handler: RuntimeSignalHandler): void;
|
|
63
64
|
onPrimitive(primitive: CanonRuntimePrimitiveId | '*', handler: RuntimePrimitiveHandler): void;
|
|
65
|
+
describeCommands(_provider?: string): ReadonlyArray<CanonRuntimeCommandDescriptor>;
|
|
66
|
+
publishRuntimeFacts(conversationId: string, facts: ReadonlyArray<CanonRuntimeFact>): Promise<void>;
|
|
67
|
+
publishRuntimeActivity(conversationId: string, item: CanonRuntimeActivityItem): Promise<void>;
|
|
68
|
+
clearRuntimeActivity(conversationId: string, options?: ClearRuntimeActivityOptions): Promise<void>;
|
|
64
69
|
/**
|
|
65
70
|
* Resolve admission live for a target user (typically read off a shared
|
|
66
71
|
* contact card) and route into either an immediate message or a contact
|
|
@@ -124,10 +129,13 @@ export declare class CanonAgent {
|
|
|
124
129
|
private clearRuntimePrimitiveRequest;
|
|
125
130
|
private prunePrimitiveRequestDedupe;
|
|
126
131
|
private handleRuntimeSignal;
|
|
132
|
+
private firstActiveTurn;
|
|
133
|
+
private publishAcceptedRuntimeSignal;
|
|
127
134
|
private abortActiveTurns;
|
|
128
135
|
private resolveBatchDeliveryIntent;
|
|
129
136
|
private notifyMessageInterrupt;
|
|
130
137
|
private createRuntimeStatePublisher;
|
|
138
|
+
private requireRuntimeStatePublisher;
|
|
131
139
|
private handleMessages;
|
|
132
140
|
private executeHandler;
|
|
133
141
|
static register(options: {
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CanonClient, buildCanonGroupContext, createRuntimeStatePublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from '@canonmsg/core';
|
|
1
|
+
import { CanonClient, buildCanonGroupContext, createRuntimeStatePublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } 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, sendMediaFileMessage, uploadMediaFile, } from './media.js';
|
|
5
|
+
import { materializeMessageMedia, materializeReplyContextMedia, 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 RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -147,6 +147,40 @@ function normalizePrimitiveArgs(value) {
|
|
|
147
147
|
}
|
|
148
148
|
return args;
|
|
149
149
|
}
|
|
150
|
+
function normalizeRuntimeFact(fact) {
|
|
151
|
+
const id = fact.id.trim();
|
|
152
|
+
const label = fact.label.trim();
|
|
153
|
+
const value = fact.value.trim();
|
|
154
|
+
if (!id || !label || !value)
|
|
155
|
+
return null;
|
|
156
|
+
return {
|
|
157
|
+
...fact,
|
|
158
|
+
id,
|
|
159
|
+
label,
|
|
160
|
+
value,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function normalizeRuntimeActivityItem(item) {
|
|
164
|
+
return {
|
|
165
|
+
...item,
|
|
166
|
+
id: item.id.trim(),
|
|
167
|
+
title: item.title.trim() || item.kind,
|
|
168
|
+
updatedAt: item.updatedAt || Date.now(),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function createTurnAbortError() {
|
|
172
|
+
const error = new Error('Canon turn was interrupted before reply delivery.');
|
|
173
|
+
error.name = 'AbortError';
|
|
174
|
+
return error;
|
|
175
|
+
}
|
|
176
|
+
function isAbortLikeError(error) {
|
|
177
|
+
if (!error || typeof error !== 'object')
|
|
178
|
+
return false;
|
|
179
|
+
const record = error;
|
|
180
|
+
if (record.name === 'AbortError' || record.code === 'ABORT_ERR')
|
|
181
|
+
return true;
|
|
182
|
+
return typeof record.message === 'string' && /\babort(?:ed)?\b/i.test(record.message);
|
|
183
|
+
}
|
|
150
184
|
export class CanonAgent {
|
|
151
185
|
options;
|
|
152
186
|
apiClient;
|
|
@@ -178,6 +212,7 @@ export class CanonAgent {
|
|
|
178
212
|
lastSeenSignal = new Map();
|
|
179
213
|
primitiveRequestDedupe = new Map();
|
|
180
214
|
activeAbortControllers = new Map();
|
|
215
|
+
activeTurns = new Map();
|
|
181
216
|
conversationMemberIds = new Map();
|
|
182
217
|
pendingMembershipChanges = new Map();
|
|
183
218
|
constructor(options) {
|
|
@@ -283,6 +318,32 @@ export class CanonAgent {
|
|
|
283
318
|
}
|
|
284
319
|
void this.publishAgentRuntime().catch(() => { });
|
|
285
320
|
}
|
|
321
|
+
describeCommands(_provider) {
|
|
322
|
+
return this.buildRuntimeDescriptor().commands ?? [];
|
|
323
|
+
}
|
|
324
|
+
async publishRuntimeFacts(conversationId, facts) {
|
|
325
|
+
this.rememberConversationId(conversationId);
|
|
326
|
+
const publisher = this.requireRuntimeStatePublisher();
|
|
327
|
+
const normalizedFacts = facts
|
|
328
|
+
.map(normalizeRuntimeFact)
|
|
329
|
+
.filter((fact) => Boolean(fact));
|
|
330
|
+
await publisher.patchRuntimeInfo(conversationId, {
|
|
331
|
+
descriptor: this.buildRuntimeDescriptor(),
|
|
332
|
+
facts: normalizedFacts,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
async publishRuntimeActivity(conversationId, item) {
|
|
336
|
+
this.rememberConversationId(conversationId);
|
|
337
|
+
const normalized = normalizeRuntimeActivityItem(item);
|
|
338
|
+
if (!normalized.id) {
|
|
339
|
+
throw new Error('Runtime activity item id is required.');
|
|
340
|
+
}
|
|
341
|
+
await this.requireRuntimeStatePublisher().writeRuntimeActivity(conversationId, normalized);
|
|
342
|
+
}
|
|
343
|
+
async clearRuntimeActivity(conversationId, options) {
|
|
344
|
+
this.rememberConversationId(conversationId);
|
|
345
|
+
await this.requireRuntimeStatePublisher().clearRuntimeActivity(conversationId, options);
|
|
346
|
+
}
|
|
286
347
|
/**
|
|
287
348
|
* Resolve admission live for a target user (typically read off a shared
|
|
288
349
|
* contact card) and route into either an immediate message or a contact
|
|
@@ -785,6 +846,7 @@ export class CanonAgent {
|
|
|
785
846
|
await Promise.resolve(rtdbWrite(`/control/${conversationId}/${this.agentId}/signal`, null)).catch(() => { });
|
|
786
847
|
return;
|
|
787
848
|
}
|
|
849
|
+
const activeTurn = this.firstActiveTurn(conversationId);
|
|
788
850
|
const abortSignal = this.abortActiveTurns(conversationId);
|
|
789
851
|
const droppedMessages = signal === 'new_session'
|
|
790
852
|
? this.sessionManager?.resetSession(conversationId) ?? []
|
|
@@ -797,6 +859,10 @@ export class CanonAgent {
|
|
|
797
859
|
return Promise.resolve();
|
|
798
860
|
return this.apiClient.updateMessageDisposition(conversationId, message.id, 'rejected').catch(() => { });
|
|
799
861
|
}));
|
|
862
|
+
await this.publishAcceptedRuntimeSignal(conversationId, signal, activeTurn, {
|
|
863
|
+
hasActiveTurn: Boolean(abortSignal),
|
|
864
|
+
droppedCount: droppedMessages.length,
|
|
865
|
+
});
|
|
800
866
|
await Promise.resolve(handler?.({
|
|
801
867
|
conversationId,
|
|
802
868
|
signal: signal,
|
|
@@ -808,6 +874,39 @@ export class CanonAgent {
|
|
|
808
874
|
});
|
|
809
875
|
await Promise.resolve(rtdbWrite(`/control/${conversationId}/${this.agentId}/signal`, null)).catch(() => { });
|
|
810
876
|
}
|
|
877
|
+
firstActiveTurn(conversationId) {
|
|
878
|
+
const turns = this.activeTurns.get(conversationId);
|
|
879
|
+
if (!turns || turns.size === 0)
|
|
880
|
+
return null;
|
|
881
|
+
return turns.values().next().value ?? null;
|
|
882
|
+
}
|
|
883
|
+
async publishAcceptedRuntimeSignal(conversationId, signal, activeTurn, outcome) {
|
|
884
|
+
if (!this.agentId)
|
|
885
|
+
return;
|
|
886
|
+
const shouldPublishInterrupted = signal === 'interrupt'
|
|
887
|
+
|| signal === 'stop_and_drop'
|
|
888
|
+
|| outcome.hasActiveTurn
|
|
889
|
+
|| outcome.droppedCount > 0;
|
|
890
|
+
const runtimeState = shouldPublishInterrupted
|
|
891
|
+
? this.createRuntimeStatePublisher()
|
|
892
|
+
: null;
|
|
893
|
+
if (runtimeState) {
|
|
894
|
+
await Promise.resolve(runtimeState.writeTurnState(conversationId, {
|
|
895
|
+
turnId: activeTurn?.turnId ?? null,
|
|
896
|
+
state: 'interrupted',
|
|
897
|
+
queueDepth: this.sessionManager?.getQueueDepth(conversationId) ?? 0,
|
|
898
|
+
currentSpeakerId: this.agentId,
|
|
899
|
+
activeMessageIds: activeTurn?.activeMessageIds ?? [],
|
|
900
|
+
capabilities: this.buildRuntimeCapabilities(),
|
|
901
|
+
...(activeTurn?.openedAt ? { openedAt: activeTurn.openedAt } : {}),
|
|
902
|
+
completedAt: { '.sv': 'timestamp' },
|
|
903
|
+
})).catch(() => { });
|
|
904
|
+
}
|
|
905
|
+
await Promise.all([
|
|
906
|
+
this.apiClient.clearStreaming(conversationId).catch(() => { }),
|
|
907
|
+
this.apiClient.setTyping(conversationId, false).catch(() => { }),
|
|
908
|
+
]);
|
|
909
|
+
}
|
|
811
910
|
abortActiveTurns(conversationId) {
|
|
812
911
|
const controllers = this.activeAbortControllers.get(conversationId);
|
|
813
912
|
if (!controllers || controllers.size === 0)
|
|
@@ -844,6 +943,13 @@ export class CanonAgent {
|
|
|
844
943
|
hostMode: false,
|
|
845
944
|
});
|
|
846
945
|
}
|
|
946
|
+
requireRuntimeStatePublisher() {
|
|
947
|
+
const publisher = this.createRuntimeStatePublisher();
|
|
948
|
+
if (!publisher) {
|
|
949
|
+
throw new Error('Canon agent must be started before publishing runtime operations.');
|
|
950
|
+
}
|
|
951
|
+
return publisher;
|
|
952
|
+
}
|
|
847
953
|
async handleMessages(conversationId, messages) {
|
|
848
954
|
if (!this.handler) {
|
|
849
955
|
console.warn(`[canon-sdk] No message handler registered — messages for ${conversationId} dropped. Call agent.on('message', handler) before starting.`);
|
|
@@ -873,9 +979,21 @@ export class CanonAgent {
|
|
|
873
979
|
const runtimeState = this.createRuntimeStatePublisher();
|
|
874
980
|
const queueDepth = () => this.sessionManager?.getQueueDepth(conversationId) ?? 0;
|
|
875
981
|
const abortController = new AbortController();
|
|
982
|
+
const throwIfAborted = () => {
|
|
983
|
+
if (abortController.signal.aborted) {
|
|
984
|
+
throw createTurnAbortError();
|
|
985
|
+
}
|
|
986
|
+
};
|
|
876
987
|
const activeControllers = this.activeAbortControllers.get(conversationId) ?? new Set();
|
|
877
988
|
activeControllers.add(abortController);
|
|
878
989
|
this.activeAbortControllers.set(conversationId, activeControllers);
|
|
990
|
+
const activeTurns = this.activeTurns.get(conversationId) ?? new Map();
|
|
991
|
+
activeTurns.set(abortController, {
|
|
992
|
+
turnId,
|
|
993
|
+
openedAt: turnOpenedAt,
|
|
994
|
+
activeMessageIds: messages.map((message) => message.id).filter(Boolean),
|
|
995
|
+
});
|
|
996
|
+
this.activeTurns.set(conversationId, activeTurns);
|
|
879
997
|
const writeTurn = async (state) => {
|
|
880
998
|
if (!runtimeState || !agentId)
|
|
881
999
|
return;
|
|
@@ -893,6 +1011,7 @@ export class CanonAgent {
|
|
|
893
1011
|
})).catch(() => { });
|
|
894
1012
|
};
|
|
895
1013
|
const setLiveState = async (state, text, streamingStatus) => {
|
|
1014
|
+
throwIfAborted();
|
|
896
1015
|
await writeTurn(state);
|
|
897
1016
|
if (streamingStatus) {
|
|
898
1017
|
try {
|
|
@@ -953,10 +1072,12 @@ export class CanonAgent {
|
|
|
953
1072
|
return;
|
|
954
1073
|
// Build reply functions
|
|
955
1074
|
const replyFinal = async (text, options) => {
|
|
1075
|
+
throwIfAborted();
|
|
956
1076
|
try {
|
|
957
1077
|
await this.apiClient.setTyping(conversationId, true, 'typing');
|
|
958
1078
|
}
|
|
959
1079
|
catch { }
|
|
1080
|
+
throwIfAborted();
|
|
960
1081
|
const sendOptions = withActiveSelfContext(options);
|
|
961
1082
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
962
1083
|
...sendOptions,
|
|
@@ -975,10 +1096,12 @@ export class CanonAgent {
|
|
|
975
1096
|
return result;
|
|
976
1097
|
};
|
|
977
1098
|
const replyProgress = async (text, options) => {
|
|
1099
|
+
throwIfAborted();
|
|
978
1100
|
await setLiveState('streaming', text, 'streaming');
|
|
979
1101
|
if (!options?.durable) {
|
|
980
1102
|
return { turnId, durable: false, messageId: null };
|
|
981
1103
|
}
|
|
1104
|
+
throwIfAborted();
|
|
982
1105
|
const { durable: _durable, ...sendOptions } = options;
|
|
983
1106
|
const sendOptionsWithContext = withActiveSelfContext(sendOptions);
|
|
984
1107
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
@@ -1000,6 +1123,9 @@ export class CanonAgent {
|
|
|
1000
1123
|
}
|
|
1001
1124
|
}
|
|
1002
1125
|
const latestMessage = hydratedMessages[hydratedMessages.length - 1] ?? null;
|
|
1126
|
+
let replyContext = latestMessage
|
|
1127
|
+
? resolveCanonReplyContext({ message: latestMessage, messages: history })
|
|
1128
|
+
: null;
|
|
1003
1129
|
const resolvedActiveSelfContextId = resolveMessageActiveSelfContextId({
|
|
1004
1130
|
messageId: latestMessage?.id,
|
|
1005
1131
|
activeSelfContextIdByMessageId: page.activeSelfContextIdByMessageId,
|
|
@@ -1021,6 +1147,18 @@ export class CanonAgent {
|
|
|
1021
1147
|
inboundPolicy: 'approval-required',
|
|
1022
1148
|
groupJoinPolicy: 'approval-required',
|
|
1023
1149
|
};
|
|
1150
|
+
if (replyContext?.found && replyContext.attachments?.length) {
|
|
1151
|
+
try {
|
|
1152
|
+
const materializedReply = await materializeReplyContextMedia(replyContext, {
|
|
1153
|
+
agentId: agent.agentId,
|
|
1154
|
+
conversationId,
|
|
1155
|
+
});
|
|
1156
|
+
replyContext = materializedReply.replyContext;
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
console.error(`[canon-sdk] Failed to materialize reply context media for ${conversationId}:`, error instanceof Error ? error.message : error);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1024
1162
|
const membershipChange = this.pendingMembershipChanges.get(conversationId) ?? null;
|
|
1025
1163
|
this.pendingMembershipChanges.delete(conversationId);
|
|
1026
1164
|
const groupContext = this.buildGroupContext({
|
|
@@ -1058,10 +1196,12 @@ export class CanonAgent {
|
|
|
1058
1196
|
});
|
|
1059
1197
|
const uploadFile = (filePath, options) => uploadMediaFile(this.apiClient, conversationId, filePath, options);
|
|
1060
1198
|
const replyWithFile = async (filePath, text = '', options) => {
|
|
1199
|
+
throwIfAborted();
|
|
1061
1200
|
try {
|
|
1062
1201
|
await this.apiClient.setTyping(conversationId, true, 'typing');
|
|
1063
1202
|
}
|
|
1064
1203
|
catch { }
|
|
1204
|
+
throwIfAborted();
|
|
1065
1205
|
try {
|
|
1066
1206
|
const result = await sendMediaFileMessage(this.apiClient, conversationId, filePath, text, {
|
|
1067
1207
|
...(options?.replyTo ? { replyTo: options.replyTo } : {}),
|
|
@@ -1093,9 +1233,11 @@ export class CanonAgent {
|
|
|
1093
1233
|
}
|
|
1094
1234
|
};
|
|
1095
1235
|
// Invoke handler
|
|
1236
|
+
throwIfAborted();
|
|
1096
1237
|
await this.handler({
|
|
1097
1238
|
messages: hydratedMessages,
|
|
1098
1239
|
history,
|
|
1240
|
+
replyContext,
|
|
1099
1241
|
conversationId,
|
|
1100
1242
|
conversation,
|
|
1101
1243
|
...(groupContext ? { groupContext } : {}),
|
|
@@ -1182,6 +1324,10 @@ export class CanonAgent {
|
|
|
1182
1324
|
}
|
|
1183
1325
|
}
|
|
1184
1326
|
catch (err) {
|
|
1327
|
+
if (abortController.signal.aborted || isAbortLikeError(err)) {
|
|
1328
|
+
await writeTurn('interrupted');
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1185
1331
|
console.error(`[canon-sdk] Handler error for ${conversationId}:`, err);
|
|
1186
1332
|
await writeTurn('interrupted');
|
|
1187
1333
|
}
|
|
@@ -1191,6 +1337,11 @@ export class CanonAgent {
|
|
|
1191
1337
|
if (activeControllers?.size === 0) {
|
|
1192
1338
|
this.activeAbortControllers.delete(conversationId);
|
|
1193
1339
|
}
|
|
1340
|
+
const activeTurns = this.activeTurns.get(conversationId);
|
|
1341
|
+
activeTurns?.delete(abortController);
|
|
1342
|
+
if (activeTurns?.size === 0) {
|
|
1343
|
+
this.activeTurns.delete(conversationId);
|
|
1344
|
+
}
|
|
1194
1345
|
clearInterval(thinkingKeepalive);
|
|
1195
1346
|
// Always clear typing when done
|
|
1196
1347
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export { CanonAgent } from './canon-agent.js';
|
|
2
2
|
export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
|
|
3
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';
|
|
4
|
+
export type { CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, } from '@canonmsg/core';
|
|
5
5
|
export { SessionManager } from './session-manager.js';
|
|
6
|
-
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
7
|
-
export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
|
|
6
|
+
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
7
|
+
export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, MaterializedCanonReplyContext, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
|
|
8
8
|
export type { SessionConfig, Session } from './session-manager.js';
|
|
9
|
-
export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, CanonMessage, CanonConversation, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
|
|
9
|
+
export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, CanonMessage, CanonConversation, CanonReplyContext, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
|
|
10
10
|
export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, 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 { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
4
|
+
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
package/dist/media.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CanonClient, type CanonMessage, type MediaAttachment, type SendMessageOptions } from '@canonmsg/core';
|
|
1
|
+
import { CanonClient, type CanonReplyContext, type CanonMessage, type MediaAttachment, type SendMessageOptions } from '@canonmsg/core';
|
|
2
2
|
export interface MaterializeMediaOptions {
|
|
3
3
|
agentId: string;
|
|
4
4
|
conversationId: string;
|
|
@@ -21,6 +21,10 @@ export interface MaterializedCanonAttachment extends MediaAttachment {
|
|
|
21
21
|
conversationId: string;
|
|
22
22
|
messageId: string;
|
|
23
23
|
}
|
|
24
|
+
export interface MaterializedCanonReplyContext {
|
|
25
|
+
replyContext: CanonReplyContext | null;
|
|
26
|
+
materialized: MaterializedCanonAttachment[];
|
|
27
|
+
}
|
|
24
28
|
/**
|
|
25
29
|
* Anthropic `image` content blocks only accept these MIME types for
|
|
26
30
|
* base64 sources. Anything outside this set must either be re-encoded or
|
|
@@ -46,6 +50,7 @@ export declare function materializeAttachment(attachment: MediaAttachment, optio
|
|
|
46
50
|
index?: number;
|
|
47
51
|
}): Promise<MaterializedCanonAttachment>;
|
|
48
52
|
export declare function materializeMessageMedia(message: Pick<CanonMessage, 'id' | 'attachments'>, options: Omit<MaterializeMediaOptions, 'messageId'>): Promise<MaterializedCanonAttachment[]>;
|
|
53
|
+
export declare function materializeReplyContextMedia(replyContext: CanonReplyContext | null, options: Omit<MaterializeMediaOptions, 'messageId'>): Promise<MaterializedCanonReplyContext>;
|
|
49
54
|
export declare function inferUploadMimeType(filePath: string, overrideMimeType?: string): string;
|
|
50
55
|
export declare function uploadMediaFile(client: CanonClient, conversationId: string, filePath: string, options?: UploadMediaFileOptions): Promise<{
|
|
51
56
|
url: string;
|
package/dist/media.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, dirname, extname, join } from 'node:path';
|
|
3
|
-
import { CANON_DIR, } from '@canonmsg/core';
|
|
3
|
+
import { CANON_DIR, renderCanonHostInboundContent, } from '@canonmsg/core';
|
|
4
4
|
const ANTHROPIC_IMAGE_MIME_TYPES = new Set([
|
|
5
5
|
'image/jpeg',
|
|
6
6
|
'image/png',
|
|
@@ -141,6 +141,31 @@ export async function materializeMessageMedia(message, options) {
|
|
|
141
141
|
index,
|
|
142
142
|
})));
|
|
143
143
|
}
|
|
144
|
+
export async function materializeReplyContextMedia(replyContext, options) {
|
|
145
|
+
if (!replyContext?.found || !replyContext.attachments?.length) {
|
|
146
|
+
return { replyContext, materialized: [] };
|
|
147
|
+
}
|
|
148
|
+
const materialized = (await Promise.all(replyContext.attachments.map((attachment, index) => attachment.url
|
|
149
|
+
? materializeAttachment(attachment, {
|
|
150
|
+
...options,
|
|
151
|
+
messageId: replyContext.messageId,
|
|
152
|
+
index,
|
|
153
|
+
})
|
|
154
|
+
: Promise.resolve(null)))).filter((attachment) => attachment !== null);
|
|
155
|
+
return {
|
|
156
|
+
replyContext: {
|
|
157
|
+
...replyContext,
|
|
158
|
+
body: renderCanonHostInboundContent({
|
|
159
|
+
text: replyContext.text,
|
|
160
|
+
contentType: replyContext.contentType,
|
|
161
|
+
attachments: replyContext.attachments,
|
|
162
|
+
contactCard: replyContext.contactCard,
|
|
163
|
+
senderType: replyContext.senderType ?? undefined,
|
|
164
|
+
}, materialized),
|
|
165
|
+
},
|
|
166
|
+
materialized,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
144
169
|
export function inferUploadMimeType(filePath, overrideMimeType) {
|
|
145
170
|
if (overrideMimeType)
|
|
146
171
|
return overrideMimeType;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeDescriptor, CanonRuntimePrimitiveId, 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, CanonGroupContext, CanonMessage, CanonConversation, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
|
|
1
|
+
export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonMessage, CanonConversation, CanonReplyContext, 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, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, 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
|
/**
|
|
@@ -37,6 +37,8 @@ export interface TurnController {
|
|
|
37
37
|
export interface MessageHandlerContext {
|
|
38
38
|
messages: CanonMessage[];
|
|
39
39
|
history: CanonMessage[];
|
|
40
|
+
/** Resolved message/media content for the latest swipe-reply target, if any. */
|
|
41
|
+
replyContext: CanonReplyContext | null;
|
|
40
42
|
conversationId: string;
|
|
41
43
|
conversation: CanonConversation;
|
|
42
44
|
/** Lightweight group awareness, present for group conversations. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
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.18.1"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|