@canonmsg/agent-sdk 1.5.1 → 1.5.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.
- package/dist/canon-agent.d.ts +2 -0
- package/dist/canon-agent.js +22 -2
- package/dist/debouncer.d.ts +1 -0
- package/dist/debouncer.js +21 -0
- package/dist/realtime.d.ts +9 -0
- package/dist/realtime.js +28 -2
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.js +36 -0
- package/dist/turn-filter.js +6 -1
- package/package.json +2 -2
package/dist/canon-agent.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare class CanonAgent {
|
|
|
55
55
|
private readonly activeTurns;
|
|
56
56
|
private readonly conversationMemberIds;
|
|
57
57
|
private readonly pendingMembershipChanges;
|
|
58
|
+
private sseConnectedLogged;
|
|
58
59
|
constructor(options: CanonAgentOptions);
|
|
59
60
|
private ensureApprovalManager;
|
|
60
61
|
private filterApprovalReplyMessages;
|
|
@@ -116,6 +117,7 @@ export declare class CanonAgent {
|
|
|
116
117
|
private hasRuntimeSignalSupport;
|
|
117
118
|
private hasRuntimePrimitiveSupport;
|
|
118
119
|
private hasRuntimeControlSupport;
|
|
120
|
+
private supportsInputInterrupt;
|
|
119
121
|
private buildRuntimeDescriptor;
|
|
120
122
|
private buildRuntimeCapabilities;
|
|
121
123
|
private publishAgentRuntime;
|
package/dist/canon-agent.js
CHANGED
|
@@ -9,6 +9,7 @@ const RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
|
|
|
9
9
|
const RUNTIME_PRIMITIVE_DEDUPE_MAX = 1_000;
|
|
10
10
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
11
11
|
supportsInterrupt: false,
|
|
12
|
+
supportsInputInterrupt: false,
|
|
12
13
|
supportsQueue: true,
|
|
13
14
|
supportsInterleave: false,
|
|
14
15
|
supportsRequiresAction: true,
|
|
@@ -218,6 +219,7 @@ export class CanonAgent {
|
|
|
218
219
|
activeTurns = new Map();
|
|
219
220
|
conversationMemberIds = new Map();
|
|
220
221
|
pendingMembershipChanges = new Map();
|
|
222
|
+
sseConnectedLogged = false;
|
|
221
223
|
constructor(options) {
|
|
222
224
|
this.options = {
|
|
223
225
|
baseUrl: 'https://api-6m6mlelskq-uc.a.run.app',
|
|
@@ -528,13 +530,21 @@ export class CanonAgent {
|
|
|
528
530
|
rtm.setConversationUpdatedHandler((payload) => {
|
|
529
531
|
this.handleConversationUpdated(payload);
|
|
530
532
|
});
|
|
533
|
+
rtm.setMessageDeletedHandler((payload) => {
|
|
534
|
+
this.sessionManager?.dropQueuedMessage(payload.conversationId, payload.messageId);
|
|
535
|
+
});
|
|
531
536
|
rtm.setConnectionHandlers({
|
|
532
|
-
onConnected: () =>
|
|
537
|
+
onConnected: () => {
|
|
538
|
+
this.startRuntimeHeartbeat();
|
|
539
|
+
if (!this.sseConnectedLogged) {
|
|
540
|
+
this.sseConnectedLogged = true;
|
|
541
|
+
console.log('[canon-sdk] SSE stream connected');
|
|
542
|
+
}
|
|
543
|
+
},
|
|
533
544
|
onDisconnected: () => this.stopRuntimeHeartbeat(),
|
|
534
545
|
});
|
|
535
546
|
this.realtimeManager = rtm;
|
|
536
547
|
await rtm.start();
|
|
537
|
-
console.log('[canon-sdk] SSE stream started');
|
|
538
548
|
}
|
|
539
549
|
async createConversation(options) {
|
|
540
550
|
return this.apiClient.createConversation(options);
|
|
@@ -633,6 +643,9 @@ export class CanonAgent {
|
|
|
633
643
|
hasRuntimeControlSupport() {
|
|
634
644
|
return this.hasRuntimeSignalSupport() || this.hasRuntimePrimitiveSupport();
|
|
635
645
|
}
|
|
646
|
+
supportsInputInterrupt() {
|
|
647
|
+
return this.hasInterruptSupport() && this.options.runtimeDescriptor?.supportsInputInterrupt !== false;
|
|
648
|
+
}
|
|
636
649
|
buildRuntimeDescriptor() {
|
|
637
650
|
const source = this.options.runtimeDescriptor ?? DEFAULT_SDK_RUNTIME_DESCRIPTOR;
|
|
638
651
|
const hasInterrupt = this.hasInterruptSupport();
|
|
@@ -677,6 +690,7 @@ export class CanonAgent {
|
|
|
677
690
|
return {
|
|
678
691
|
...source,
|
|
679
692
|
supportsInterrupt: hasInterrupt,
|
|
693
|
+
supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
|
|
680
694
|
commands,
|
|
681
695
|
actions,
|
|
682
696
|
};
|
|
@@ -685,6 +699,7 @@ export class CanonAgent {
|
|
|
685
699
|
return {
|
|
686
700
|
...SDK_RUNTIME_CAPABILITIES,
|
|
687
701
|
supportsInterrupt: this.hasInterruptSupport(),
|
|
702
|
+
supportsInputInterrupt: this.supportsInputInterrupt(),
|
|
688
703
|
supportsQueue: Boolean(this.sessionManager),
|
|
689
704
|
};
|
|
690
705
|
}
|
|
@@ -1049,6 +1064,10 @@ export class CanonAgent {
|
|
|
1049
1064
|
if (!runtimeState || !agentId)
|
|
1050
1065
|
return;
|
|
1051
1066
|
turnState = state;
|
|
1067
|
+
const isOpenTurn = state === 'thinking'
|
|
1068
|
+
|| state === 'streaming'
|
|
1069
|
+
|| state === 'tool'
|
|
1070
|
+
|| state === 'waiting_input';
|
|
1052
1071
|
await Promise.resolve(runtimeState.writeTurnState(conversationId, {
|
|
1053
1072
|
turnId,
|
|
1054
1073
|
state,
|
|
@@ -1056,6 +1075,7 @@ export class CanonAgent {
|
|
|
1056
1075
|
currentSpeakerId: agentId,
|
|
1057
1076
|
capabilities: this.buildRuntimeCapabilities(),
|
|
1058
1077
|
openedAt: turnOpenedAt,
|
|
1078
|
+
...(isOpenTurn ? { turnUpdatedAt: Date.now() } : {}),
|
|
1059
1079
|
...(state === 'completed' || state === 'interrupted' || state === 'idle'
|
|
1060
1080
|
? { completedAt: { '.sv': 'timestamp' } }
|
|
1061
1081
|
: {}),
|
package/dist/debouncer.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class Debouncer {
|
|
|
9
9
|
constructor(debounceMs: number);
|
|
10
10
|
setCallback(cb: (conversationId: string, messages: CanonMessage[], provenanceByMessageId: ReadonlyMap<string, CanonRuntimeProvenance>) => void): void;
|
|
11
11
|
add(conversationId: string, message: CanonMessage, provenance?: CanonRuntimeProvenance | null): void;
|
|
12
|
+
removeMessage(conversationId: string, messageId: string): boolean;
|
|
12
13
|
private flush;
|
|
13
14
|
destroy(): void;
|
|
14
15
|
}
|
package/dist/debouncer.js
CHANGED
|
@@ -43,6 +43,27 @@ export class Debouncer {
|
|
|
43
43
|
this.flush(conversationId);
|
|
44
44
|
}, this.debounceMs));
|
|
45
45
|
}
|
|
46
|
+
removeMessage(conversationId, messageId) {
|
|
47
|
+
const existing = this.pending.get(conversationId);
|
|
48
|
+
if (!existing || existing.length === 0)
|
|
49
|
+
return false;
|
|
50
|
+
const next = existing.filter((message) => message.id !== messageId);
|
|
51
|
+
if (next.length === existing.length)
|
|
52
|
+
return false;
|
|
53
|
+
this.provenanceByMessageId.delete(messageId);
|
|
54
|
+
if (next.length === 0) {
|
|
55
|
+
this.pending.delete(conversationId);
|
|
56
|
+
this.orderedFlags.delete(conversationId);
|
|
57
|
+
const timer = this.timers.get(conversationId);
|
|
58
|
+
if (timer)
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
this.timers.delete(conversationId);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.pending.set(conversationId, next);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
46
67
|
flush(conversationId) {
|
|
47
68
|
const messages = this.pending.get(conversationId);
|
|
48
69
|
const isOrdered = this.orderedFlags.get(conversationId) ?? false;
|
package/dist/realtime.d.ts
CHANGED
|
@@ -10,15 +10,20 @@ export declare class RealtimeManager {
|
|
|
10
10
|
private agentId;
|
|
11
11
|
private stream;
|
|
12
12
|
private running;
|
|
13
|
+
private lastSseErrorKey;
|
|
14
|
+
private lastSseErrorAt;
|
|
15
|
+
private suppressedSseErrorCount;
|
|
13
16
|
private onAgentContext;
|
|
14
17
|
private onContactRequest;
|
|
15
18
|
private onContactApproved;
|
|
16
19
|
private onContactAdded;
|
|
17
20
|
private onContactRemoved;
|
|
18
21
|
private onConversationUpdated;
|
|
22
|
+
private onMessageDeleted;
|
|
19
23
|
private onConnected;
|
|
20
24
|
private onDisconnected;
|
|
21
25
|
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, apiClient?: CanonClient);
|
|
26
|
+
private logSseError;
|
|
22
27
|
setOnAgentContext(cb: (ctx: AgentContext) => void): void;
|
|
23
28
|
setContactRequestHandlers(handlers: {
|
|
24
29
|
onContactRequest?: (payload: ContactRequestPayload) => void;
|
|
@@ -29,6 +34,10 @@ export declare class RealtimeManager {
|
|
|
29
34
|
onContactRemoved?: (payload: ContactRemovedPayload) => void;
|
|
30
35
|
}): void;
|
|
31
36
|
setConversationUpdatedHandler(cb: (payload: ConversationUpdatedPayload) => void): void;
|
|
37
|
+
setMessageDeletedHandler(cb: (payload: {
|
|
38
|
+
conversationId: string;
|
|
39
|
+
messageId: string;
|
|
40
|
+
}) => void): void;
|
|
32
41
|
setConnectionHandlers(handlers: {
|
|
33
42
|
onConnected?: () => void;
|
|
34
43
|
onDisconnected?: () => void;
|
package/dist/realtime.js
CHANGED
|
@@ -9,12 +9,16 @@ export class RealtimeManager {
|
|
|
9
9
|
agentId;
|
|
10
10
|
stream;
|
|
11
11
|
running = false;
|
|
12
|
+
lastSseErrorKey = null;
|
|
13
|
+
lastSseErrorAt = 0;
|
|
14
|
+
suppressedSseErrorCount = 0;
|
|
12
15
|
onAgentContext = null;
|
|
13
16
|
onContactRequest = null;
|
|
14
17
|
onContactApproved = null;
|
|
15
18
|
onContactAdded = null;
|
|
16
19
|
onContactRemoved = null;
|
|
17
20
|
onConversationUpdated = null;
|
|
21
|
+
onMessageDeleted = null;
|
|
18
22
|
onConnected = null;
|
|
19
23
|
onDisconnected = null;
|
|
20
24
|
constructor(apiKey, debouncer, agentId, streamUrl, apiClient) {
|
|
@@ -54,6 +58,10 @@ export class RealtimeManager {
|
|
|
54
58
|
};
|
|
55
59
|
this.debouncer.add(payload.conversationId, message, payload.provenance ?? null);
|
|
56
60
|
},
|
|
61
|
+
onMessageDeleted: (payload) => {
|
|
62
|
+
this.debouncer.removeMessage(payload.conversationId, payload.messageId);
|
|
63
|
+
this.onMessageDeleted?.(payload);
|
|
64
|
+
},
|
|
57
65
|
onAgentContext: (ctx) => {
|
|
58
66
|
this.onAgentContext?.(ctx);
|
|
59
67
|
},
|
|
@@ -80,11 +88,27 @@ export class RealtimeManager {
|
|
|
80
88
|
this.onDisconnected?.();
|
|
81
89
|
},
|
|
82
90
|
onError: (err) => {
|
|
83
|
-
|
|
91
|
+
this.logSseError(err);
|
|
84
92
|
},
|
|
85
93
|
},
|
|
86
94
|
});
|
|
87
95
|
}
|
|
96
|
+
logSseError(err) {
|
|
97
|
+
const code = err.code;
|
|
98
|
+
const key = `${typeof code === 'string' ? code : 'generic'}:${err.message}`;
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
if (this.lastSseErrorKey === key && now - this.lastSseErrorAt < 60_000) {
|
|
101
|
+
this.suppressedSseErrorCount += 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (this.suppressedSseErrorCount > 0) {
|
|
105
|
+
console.error(`[canon-sdk] SSE error repeated ${this.suppressedSseErrorCount} more time${this.suppressedSseErrorCount === 1 ? '' : 's'}`);
|
|
106
|
+
this.suppressedSseErrorCount = 0;
|
|
107
|
+
}
|
|
108
|
+
this.lastSseErrorKey = key;
|
|
109
|
+
this.lastSseErrorAt = now;
|
|
110
|
+
console.error('[canon-sdk] SSE error:', err.message);
|
|
111
|
+
}
|
|
88
112
|
setOnAgentContext(cb) {
|
|
89
113
|
this.onAgentContext = cb;
|
|
90
114
|
}
|
|
@@ -99,6 +123,9 @@ export class RealtimeManager {
|
|
|
99
123
|
setConversationUpdatedHandler(cb) {
|
|
100
124
|
this.onConversationUpdated = cb;
|
|
101
125
|
}
|
|
126
|
+
setMessageDeletedHandler(cb) {
|
|
127
|
+
this.onMessageDeleted = cb;
|
|
128
|
+
}
|
|
102
129
|
setConnectionHandlers(handlers) {
|
|
103
130
|
this.onConnected = handlers.onConnected ?? null;
|
|
104
131
|
this.onDisconnected = handlers.onDisconnected ?? null;
|
|
@@ -110,6 +137,5 @@ export class RealtimeManager {
|
|
|
110
137
|
stop() {
|
|
111
138
|
this.running = false;
|
|
112
139
|
this.stream.stop();
|
|
113
|
-
this.onDisconnected?.();
|
|
114
140
|
}
|
|
115
141
|
}
|
|
@@ -63,6 +63,8 @@ export declare class SessionManager {
|
|
|
63
63
|
get sessionCount(): number;
|
|
64
64
|
/** Number of queued batches waiting behind the active turn for this conversation. */
|
|
65
65
|
getQueueDepth(conversationId: string): number;
|
|
66
|
+
/** Drop one not-yet-running queued message for a conversation. */
|
|
67
|
+
dropQueuedMessage(conversationId: string, messageId: string): CanonMessage[];
|
|
66
68
|
/** Drop queued, not-yet-running batches for a conversation. */
|
|
67
69
|
dropQueued(conversationId: string): CanonMessage[];
|
|
68
70
|
/** Drop queued work and clear retained context for a conversation. */
|
package/dist/session-manager.js
CHANGED
|
@@ -199,6 +199,42 @@ export class SessionManager {
|
|
|
199
199
|
getQueueDepth(conversationId) {
|
|
200
200
|
return this.queues.get(conversationId)?.length ?? 0;
|
|
201
201
|
}
|
|
202
|
+
/** Drop one not-yet-running queued message for a conversation. */
|
|
203
|
+
dropQueuedMessage(conversationId, messageId) {
|
|
204
|
+
const queue = this.queues.get(conversationId);
|
|
205
|
+
if (!queue || queue.length === 0)
|
|
206
|
+
return [];
|
|
207
|
+
const droppedMessages = [];
|
|
208
|
+
const nextQueue = [];
|
|
209
|
+
for (const item of queue) {
|
|
210
|
+
const keptMessages = item.messages.filter((message) => {
|
|
211
|
+
const drop = message.id === messageId;
|
|
212
|
+
if (drop)
|
|
213
|
+
droppedMessages.push(message);
|
|
214
|
+
return !drop;
|
|
215
|
+
});
|
|
216
|
+
if (keptMessages.length === 0) {
|
|
217
|
+
item.resolve();
|
|
218
|
+
}
|
|
219
|
+
else if (keptMessages.length !== item.messages.length) {
|
|
220
|
+
nextQueue.push({ ...item, messages: keptMessages });
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
nextQueue.push(item);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (droppedMessages.length === 0)
|
|
227
|
+
return [];
|
|
228
|
+
if (nextQueue.length === 0) {
|
|
229
|
+
this.queues.delete(conversationId);
|
|
230
|
+
this.pending.delete(conversationId);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
this.queues.set(conversationId, nextQueue);
|
|
234
|
+
}
|
|
235
|
+
this.removeMessagesFromSession(conversationId, droppedMessages);
|
|
236
|
+
return droppedMessages;
|
|
237
|
+
}
|
|
202
238
|
/** Drop queued, not-yet-running batches for a conversation. */
|
|
203
239
|
dropQueued(conversationId) {
|
|
204
240
|
const queue = this.queues.get(conversationId);
|
package/dist/turn-filter.js
CHANGED
|
@@ -2,7 +2,12 @@ import { evaluateParticipationPolicy, normalizeTurnState, rtdbRead, shouldTrigge
|
|
|
2
2
|
function normalizeRuntimeTurnState(value) {
|
|
3
3
|
const turnState = normalizeTurnState(value);
|
|
4
4
|
if (turnState) {
|
|
5
|
-
return {
|
|
5
|
+
return {
|
|
6
|
+
state: turnState.state,
|
|
7
|
+
...(turnState.openedAt !== undefined ? { openedAt: turnState.openedAt } : {}),
|
|
8
|
+
...(turnState.updatedAt !== undefined ? { updatedAt: turnState.updatedAt } : {}),
|
|
9
|
+
...(turnState.turnUpdatedAt !== undefined ? { turnUpdatedAt: turnState.turnUpdatedAt } : {}),
|
|
10
|
+
};
|
|
6
11
|
}
|
|
7
12
|
return null;
|
|
8
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.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.
|
|
31
|
+
"@canonmsg/core": "^0.20.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|