@canonmsg/agent-sdk 0.10.1 → 1.0.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 +10 -18
- package/dist/canon-agent.d.ts +1 -1
- package/dist/canon-agent.js +56 -57
- package/dist/index.d.ts +1 -1
- package/dist/turn-filter.js +0 -9
- package/dist/types.d.ts +7 -13
- package/package.json +2 -2
- package/dist/polling.d.ts +0 -18
- package/dist/polling.js +0 -99
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ const agent = new CanonAgent({
|
|
|
12
12
|
historyLimit: 30,
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
agent.on('message', async ({ messages, history,
|
|
15
|
+
agent.on('message', async ({ messages, history, replyFinal }) => {
|
|
16
16
|
const response = await callMyLLM(messages, history);
|
|
17
|
-
await
|
|
17
|
+
await replyFinal(response);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
await agent.start();
|
|
@@ -35,8 +35,7 @@ No additional dependencies required — the SDK uses native `fetch` and `Readabl
|
|
|
35
35
|
| `apiKey` | `string` | **required** | API key obtained after agent registration approval |
|
|
36
36
|
| `baseUrl` | `string` | Canon production URL | Override the API base URL |
|
|
37
37
|
| `streamUrl` | `string` | Canon stream service URL | Override the SSE stream URL |
|
|
38
|
-
| `deliveryMode` | `'auto' \| 'sse'
|
|
39
|
-
| `pollingIntervalMs` | `number` | `3000` | Polling interval in milliseconds (polling mode only) |
|
|
38
|
+
| `deliveryMode` | `'auto' \| 'sse'` | `'auto'` | How the SDK receives new messages |
|
|
40
39
|
| `debounceMs` | `number` | `2000` | Batching window for incoming messages per conversation |
|
|
41
40
|
| `historyLimit` | `number` | `50` | Number of historical messages to fetch (max 100) |
|
|
42
41
|
| `sessions` | `SessionOptions` | `undefined` | Enable per-conversation session queues and persistent metadata |
|
|
@@ -110,11 +109,11 @@ Current rules of thumb:
|
|
|
110
109
|
|
|
111
110
|
## Delivery Modes
|
|
112
111
|
|
|
113
|
-
The SDK supports
|
|
112
|
+
The SDK supports SSE-backed delivery modes for receiving messages:
|
|
114
113
|
|
|
115
114
|
### `auto` (default)
|
|
116
115
|
|
|
117
|
-
Uses `sse`.
|
|
116
|
+
Uses `sse`.
|
|
118
117
|
|
|
119
118
|
### `sse`
|
|
120
119
|
|
|
@@ -122,23 +121,16 @@ Connects to Canon's SSE stream service for instant message delivery. A single co
|
|
|
122
121
|
|
|
123
122
|
Best for: agents in a small-to-medium number of active conversations where low latency matters.
|
|
124
123
|
|
|
125
|
-
### `polling`
|
|
126
|
-
|
|
127
|
-
Periodically calls the REST API to discover new messages. Latency is bounded by `pollingIntervalMs`.
|
|
128
|
-
|
|
129
|
-
Best for: agents in many conversations, or environments where long-lived connections are not practical.
|
|
130
|
-
|
|
131
124
|
## Message Handler
|
|
132
125
|
|
|
133
126
|
The `message` event handler receives a context object with:
|
|
134
127
|
|
|
135
128
|
| Field | Type | Description |
|
|
136
129
|
|---|---|---|
|
|
137
|
-
| `messages` | `
|
|
138
|
-
| `history` | `
|
|
130
|
+
| `messages` | `CanonMessage[]` | New messages in this batch (debounced, sorted by time) |
|
|
131
|
+
| `history` | `CanonMessage[]` | Last N messages before these new ones |
|
|
139
132
|
| `conversationId` | `string` | The conversation these messages belong to |
|
|
140
|
-
| `conversation` | `
|
|
141
|
-
| `reply` | `(text: string, options?) => Promise<{ messageId: string }>` | Convenience alias for `replyFinal` |
|
|
133
|
+
| `conversation` | `CanonConversation` | Full conversation metadata |
|
|
142
134
|
| `replyFinal` | `(text: string, options?) => Promise<{ messageId: string }>` | Send the durable final reply for a turn |
|
|
143
135
|
| `replyProgress` | `(text: string, options?) => Promise<{ turnId: string; durable: boolean; messageId: string \| null }>` | Update the live turn progress; add `durable: true` to also persist it |
|
|
144
136
|
| `agent` | `AgentContext` | Trusted Canon agent identity and access context |
|
|
@@ -255,9 +247,9 @@ The SDK exports `CanonApiError` for typed error handling:
|
|
|
255
247
|
```typescript
|
|
256
248
|
import { CanonAgent, CanonApiError } from '@canonmsg/agent-sdk';
|
|
257
249
|
|
|
258
|
-
agent.on('message', async ({ messages,
|
|
250
|
+
agent.on('message', async ({ messages, replyFinal }) => {
|
|
259
251
|
try {
|
|
260
|
-
await
|
|
252
|
+
await replyFinal('Hello!');
|
|
261
253
|
} catch (err) {
|
|
262
254
|
if (err instanceof CanonApiError) {
|
|
263
255
|
console.error(`API error ${err.status}: ${err.message}`);
|
package/dist/canon-agent.d.ts
CHANGED
|
@@ -23,7 +23,6 @@ export declare class CanonAgent {
|
|
|
23
23
|
private apiClient;
|
|
24
24
|
private authManager;
|
|
25
25
|
private debouncer;
|
|
26
|
-
private pollingManager;
|
|
27
26
|
private realtimeManager;
|
|
28
27
|
private sessionManager;
|
|
29
28
|
private handler;
|
|
@@ -91,6 +90,7 @@ export declare class CanonAgent {
|
|
|
91
90
|
private startRuntimeHeartbeat;
|
|
92
91
|
private stopRuntimeHeartbeat;
|
|
93
92
|
private clearAgentRuntime;
|
|
93
|
+
private createRuntimeStatePublisher;
|
|
94
94
|
private handleMessages;
|
|
95
95
|
private executeHandler;
|
|
96
96
|
static register(options: {
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { CanonClient, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, mergeWorkSessionContexts,
|
|
1
|
+
import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, mergeWorkSessionContexts, } 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
5
|
import { materializeMessageMedia, uploadMediaFile, } from './media.js';
|
|
6
|
-
import { PollingManager } from './polling.js';
|
|
7
6
|
import { SessionManager } from './session-manager.js';
|
|
8
7
|
const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
|
|
9
8
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
@@ -27,7 +26,6 @@ export class CanonAgent {
|
|
|
27
26
|
apiClient;
|
|
28
27
|
authManager;
|
|
29
28
|
debouncer;
|
|
30
|
-
pollingManager = null;
|
|
31
29
|
realtimeManager = null;
|
|
32
30
|
sessionManager = null;
|
|
33
31
|
handler = null;
|
|
@@ -49,7 +47,6 @@ export class CanonAgent {
|
|
|
49
47
|
this.options = {
|
|
50
48
|
baseUrl: 'https://api-6m6mlelskq-uc.a.run.app',
|
|
51
49
|
deliveryMode: 'auto',
|
|
52
|
-
pollingIntervalMs: 3000,
|
|
53
50
|
debounceMs: 2000,
|
|
54
51
|
historyLimit: 50,
|
|
55
52
|
autoMarkRead: true,
|
|
@@ -187,6 +184,9 @@ export class CanonAgent {
|
|
|
187
184
|
mode = 'sse';
|
|
188
185
|
console.log(`[canon-sdk] Auto-selected ${mode} mode (${conversations.length} conversations)`);
|
|
189
186
|
}
|
|
187
|
+
if (mode !== 'sse') {
|
|
188
|
+
throw new Error(`Unsupported deliveryMode: ${mode}. Use 'auto' or 'sse'.`);
|
|
189
|
+
}
|
|
190
190
|
// 3b. Fetch agent context (identity, owner, access level)
|
|
191
191
|
try {
|
|
192
192
|
this.agentContext = await this.apiClient.getAgentMe();
|
|
@@ -196,8 +196,9 @@ export class CanonAgent {
|
|
|
196
196
|
}
|
|
197
197
|
// 3c. Initialize RTDB session state reporting (opt-in)
|
|
198
198
|
if (this.options.sessionState) {
|
|
199
|
+
const runtimeState = this.createRuntimeStatePublisher();
|
|
199
200
|
for (const id of this.cachedConversationIds) {
|
|
200
|
-
writeSessionState(id,
|
|
201
|
+
runtimeState?.writeSessionState(id, {
|
|
201
202
|
cwd: process.cwd(),
|
|
202
203
|
isActive: true,
|
|
203
204
|
...(this.options.clientType ? { clientType: this.options.clientType } : {}),
|
|
@@ -208,41 +209,34 @@ export class CanonAgent {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
// 4. Start delivery
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
console.log('[canon-sdk] SSE stream started');
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
this.pollingManager = new PollingManager(this.apiClient, this.debouncer, agentId, this.options.pollingIntervalMs, () => this.startRuntimeHeartbeat(), () => this.stopRuntimeHeartbeat());
|
|
243
|
-
await this.pollingManager.start();
|
|
244
|
-
console.log(`[canon-sdk] Polling started (interval: ${this.options.pollingIntervalMs}ms)`);
|
|
245
|
-
}
|
|
212
|
+
const { RealtimeManager } = await import('./realtime.js');
|
|
213
|
+
const rtm = new RealtimeManager(this.options.apiKey, this.debouncer, agentId, this.options.streamUrl, this.apiClient);
|
|
214
|
+
rtm.setOnAgentContext((ctx) => {
|
|
215
|
+
this.agentContext = ctx;
|
|
216
|
+
});
|
|
217
|
+
rtm.setContactRequestHandlers({
|
|
218
|
+
onContactRequest: (request) => {
|
|
219
|
+
void this.handleContactRequestEvent(this.contactRequestHandler, request);
|
|
220
|
+
},
|
|
221
|
+
onContactApproved: (request) => {
|
|
222
|
+
void this.handleContactRequestEvent(this.contactApprovedHandler, request);
|
|
223
|
+
},
|
|
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
|
+
});
|
|
233
|
+
rtm.setConnectionHandlers({
|
|
234
|
+
onConnected: () => this.startRuntimeHeartbeat(),
|
|
235
|
+
onDisconnected: () => this.stopRuntimeHeartbeat(),
|
|
236
|
+
});
|
|
237
|
+
this.realtimeManager = rtm;
|
|
238
|
+
await rtm.start();
|
|
239
|
+
console.log('[canon-sdk] SSE stream started');
|
|
246
240
|
}
|
|
247
241
|
async createConversation(options) {
|
|
248
242
|
return this.apiClient.createConversation(options);
|
|
@@ -304,18 +298,18 @@ export class CanonAgent {
|
|
|
304
298
|
return;
|
|
305
299
|
this.running = false;
|
|
306
300
|
// Clear session state if enabled (uses cached IDs — no network call during shutdown)
|
|
307
|
-
|
|
301
|
+
const runtimeState = this.createRuntimeStatePublisher();
|
|
302
|
+
if (this.options.sessionState && runtimeState) {
|
|
308
303
|
for (const id of this.cachedConversationIds) {
|
|
309
|
-
Promise.resolve(clearSessionState(id
|
|
304
|
+
Promise.resolve(runtimeState.clearSessionState(id)).catch(() => { });
|
|
310
305
|
}
|
|
311
306
|
}
|
|
312
|
-
if (
|
|
307
|
+
if (runtimeState) {
|
|
313
308
|
for (const id of this.cachedConversationIds) {
|
|
314
|
-
Promise.resolve(clearTurnState(id
|
|
309
|
+
Promise.resolve(runtimeState.clearTurnState(id)).catch(() => { });
|
|
315
310
|
}
|
|
316
311
|
}
|
|
317
312
|
await this.clearAgentRuntime();
|
|
318
|
-
this.pollingManager?.stop();
|
|
319
313
|
this.realtimeManager?.stop();
|
|
320
314
|
this.sessionManager?.destroy();
|
|
321
315
|
this.authManager.destroy();
|
|
@@ -323,13 +317,11 @@ export class CanonAgent {
|
|
|
323
317
|
console.log('[canon-sdk] Stopped');
|
|
324
318
|
}
|
|
325
319
|
async publishAgentRuntime() {
|
|
326
|
-
|
|
320
|
+
const publisher = this.createRuntimeStatePublisher();
|
|
321
|
+
if (!publisher)
|
|
327
322
|
return;
|
|
328
|
-
await
|
|
329
|
-
clientType: this.options.clientType ?? 'generic',
|
|
330
|
-
hostMode: false,
|
|
323
|
+
await publisher.publishAgentRuntime({
|
|
331
324
|
runtimeDescriptor: this.options.runtimeDescriptor ?? DEFAULT_SDK_RUNTIME_DESCRIPTOR,
|
|
332
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
333
325
|
});
|
|
334
326
|
}
|
|
335
327
|
startRuntimeHeartbeat() {
|
|
@@ -349,9 +341,16 @@ export class CanonAgent {
|
|
|
349
341
|
void this.clearAgentRuntime();
|
|
350
342
|
}
|
|
351
343
|
async clearAgentRuntime() {
|
|
344
|
+
await Promise.resolve(this.createRuntimeStatePublisher()?.clearAgentRuntime()).catch(() => { });
|
|
345
|
+
}
|
|
346
|
+
createRuntimeStatePublisher() {
|
|
352
347
|
if (!this.agentId)
|
|
353
|
-
return;
|
|
354
|
-
|
|
348
|
+
return null;
|
|
349
|
+
return createRuntimeStatePublisher({
|
|
350
|
+
agentId: this.agentId,
|
|
351
|
+
clientType: this.options.clientType ?? 'generic',
|
|
352
|
+
hostMode: false,
|
|
353
|
+
});
|
|
355
354
|
}
|
|
356
355
|
async handleMessages(conversationId, messages) {
|
|
357
356
|
if (!this.handler) {
|
|
@@ -375,12 +374,13 @@ export class CanonAgent {
|
|
|
375
374
|
let turnState = 'thinking';
|
|
376
375
|
let shouldPersistTurnState = false;
|
|
377
376
|
const agentId = this.agentId;
|
|
377
|
+
const runtimeState = this.createRuntimeStatePublisher();
|
|
378
378
|
const queueDepth = () => this.sessionManager?.getQueueDepth(conversationId) ?? 0;
|
|
379
379
|
const writeTurn = async (state) => {
|
|
380
|
-
if (!agentId)
|
|
380
|
+
if (!runtimeState || !agentId)
|
|
381
381
|
return;
|
|
382
382
|
turnState = state;
|
|
383
|
-
await Promise.resolve(writeTurnState(conversationId,
|
|
383
|
+
await Promise.resolve(runtimeState.writeTurnState(conversationId, {
|
|
384
384
|
turnId,
|
|
385
385
|
state,
|
|
386
386
|
queueDepth: queueDepth(),
|
|
@@ -583,7 +583,6 @@ export class CanonAgent {
|
|
|
583
583
|
history,
|
|
584
584
|
conversationId,
|
|
585
585
|
conversation,
|
|
586
|
-
reply: replyFinal,
|
|
587
586
|
replyFinal,
|
|
588
587
|
replyProgress,
|
|
589
588
|
deleteMessage,
|
|
@@ -682,8 +681,8 @@ export class CanonAgent {
|
|
|
682
681
|
await this.apiClient.clearStreaming(conversationId);
|
|
683
682
|
}
|
|
684
683
|
catch { }
|
|
685
|
-
if (
|
|
686
|
-
await Promise.resolve(clearTurnState(conversationId
|
|
684
|
+
if (runtimeState && !shouldPersistTurnState) {
|
|
685
|
+
await Promise.resolve(runtimeState.clearTurnState(conversationId)).catch(() => { });
|
|
687
686
|
}
|
|
688
687
|
}
|
|
689
688
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export { getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthro
|
|
|
7
7
|
export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
|
|
8
8
|
export type { SessionConfig, Session } from './session-manager.js';
|
|
9
9
|
export type { AgentContext, CanonContactRequest, CanonMessage, CanonConversation, CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, SendMessageOptions, CreateConversationOptions, UpdateWorkSessionConversationOptions, } from '@canonmsg/core';
|
|
10
|
-
export type {
|
|
10
|
+
export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
package/dist/turn-filter.js
CHANGED
|
@@ -4,15 +4,6 @@ function normalizeRuntimeTurnState(value) {
|
|
|
4
4
|
if (turnState) {
|
|
5
5
|
return { state: turnState.state };
|
|
6
6
|
}
|
|
7
|
-
if (!value || typeof value !== 'object')
|
|
8
|
-
return null;
|
|
9
|
-
const state = value.state;
|
|
10
|
-
if (state === 'running') {
|
|
11
|
-
return { state: 'streaming' };
|
|
12
|
-
}
|
|
13
|
-
if (state === 'requires_action') {
|
|
14
|
-
return { state: 'waiting_input' };
|
|
15
|
-
}
|
|
16
7
|
return null;
|
|
17
8
|
}
|
|
18
9
|
export async function shouldDispatchInboundMessage(conversationId, agentId, message, options) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
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
2
|
import type { AddMemberResult, CanonMessage, CanonConversation, CreateWorkSessionOptions, SendMessageOptions, UpdateWorkSessionConversationOptions } from '@canonmsg/core';
|
|
3
3
|
import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
|
|
4
|
-
export type SDKMessage = CanonMessage;
|
|
5
|
-
export type SDKConversation = CanonConversation;
|
|
6
4
|
export interface ProgressMessageOptions extends SendMessageOptions {
|
|
7
5
|
/**
|
|
8
6
|
* Persist the progress update to Firestore.
|
|
@@ -23,7 +21,7 @@ export interface SessionInfo {
|
|
|
23
21
|
/** Session ID (= conversationId) */
|
|
24
22
|
id: string;
|
|
25
23
|
/** All accumulated messages for this conversation (within the context limit), oldest first */
|
|
26
|
-
messages:
|
|
24
|
+
messages: CanonMessage[];
|
|
27
25
|
/** Arbitrary per-session state the agent can read/write across handler calls */
|
|
28
26
|
metadata: Record<string, unknown>;
|
|
29
27
|
queueDepth?: number;
|
|
@@ -37,13 +35,10 @@ export interface TurnController {
|
|
|
37
35
|
setWaitingInput: (text?: string) => Promise<void>;
|
|
38
36
|
}
|
|
39
37
|
export interface MessageHandlerContext {
|
|
40
|
-
messages:
|
|
41
|
-
history:
|
|
38
|
+
messages: CanonMessage[];
|
|
39
|
+
history: CanonMessage[];
|
|
42
40
|
conversationId: string;
|
|
43
|
-
conversation:
|
|
44
|
-
reply: (text: string, options?: SendMessageOptions) => Promise<{
|
|
45
|
-
messageId: string;
|
|
46
|
-
}>;
|
|
41
|
+
conversation: CanonConversation;
|
|
47
42
|
replyFinal: (text: string, options?: SendMessageOptions) => Promise<{
|
|
48
43
|
messageId: string;
|
|
49
44
|
}>;
|
|
@@ -82,7 +77,7 @@ export interface MessageHandlerContext {
|
|
|
82
77
|
activeWorkSessions?: import('@canonmsg/core').CanonWorkSessionContext[];
|
|
83
78
|
/** Canon-managed local media access for the current conversation. */
|
|
84
79
|
media: {
|
|
85
|
-
materialize: (message?:
|
|
80
|
+
materialize: (message?: CanonMessage, options?: Omit<MaterializeMediaOptions, 'agentId' | 'conversationId' | 'messageId'>) => Promise<MaterializedCanonAttachment[]>;
|
|
86
81
|
uploadFile: (filePath: string, options?: UploadMediaFileOptions) => Promise<{
|
|
87
82
|
url: string;
|
|
88
83
|
attachment: import('@canonmsg/core').MediaAttachment;
|
|
@@ -107,14 +102,13 @@ export interface SessionOptions {
|
|
|
107
102
|
/** Evict idle sessions after this many ms (default: 3600000 = 1h) */
|
|
108
103
|
idleTimeoutMs?: number;
|
|
109
104
|
}
|
|
110
|
-
export type DeliveryMode = 'auto' | 'sse'
|
|
105
|
+
export type DeliveryMode = 'auto' | 'sse';
|
|
111
106
|
export interface CanonAgentOptions {
|
|
112
107
|
apiKey: string;
|
|
113
108
|
baseUrl?: string;
|
|
114
109
|
streamUrl?: string;
|
|
115
|
-
/** `auto`
|
|
110
|
+
/** `auto` resolves to SSE. */
|
|
116
111
|
deliveryMode?: DeliveryMode;
|
|
117
|
-
pollingIntervalMs?: number;
|
|
118
112
|
debounceMs?: number;
|
|
119
113
|
historyLimit?: number;
|
|
120
114
|
/** Automatically mark conversations as read when handling messages (default: true) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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.15.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
package/dist/polling.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { CanonClient } from '@canonmsg/core';
|
|
2
|
-
import { Debouncer } from './debouncer.js';
|
|
3
|
-
export declare class PollingManager {
|
|
4
|
-
private apiClient;
|
|
5
|
-
private debouncer;
|
|
6
|
-
private agentId;
|
|
7
|
-
private pollingIntervalMs;
|
|
8
|
-
private onHealthy;
|
|
9
|
-
private onUnhealthy;
|
|
10
|
-
private lastSeenTimestamps;
|
|
11
|
-
private pollTimer;
|
|
12
|
-
private running;
|
|
13
|
-
constructor(apiClient: CanonClient, debouncer: Debouncer, agentId: string, pollingIntervalMs: number, onHealthy?: () => void, onUnhealthy?: () => void);
|
|
14
|
-
start(): Promise<void>;
|
|
15
|
-
private poll;
|
|
16
|
-
private findActiveConversations;
|
|
17
|
-
stop(): void;
|
|
18
|
-
}
|
package/dist/polling.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { buildParticipationHistorySnapshots } from './policy-history.js';
|
|
2
|
-
import { shouldDispatchInboundMessage } from './turn-filter.js';
|
|
3
|
-
export class PollingManager {
|
|
4
|
-
apiClient;
|
|
5
|
-
debouncer;
|
|
6
|
-
agentId;
|
|
7
|
-
pollingIntervalMs;
|
|
8
|
-
onHealthy;
|
|
9
|
-
onUnhealthy;
|
|
10
|
-
lastSeenTimestamps = new Map();
|
|
11
|
-
pollTimer = null;
|
|
12
|
-
running = false;
|
|
13
|
-
constructor(apiClient, debouncer, agentId, pollingIntervalMs, onHealthy, onUnhealthy) {
|
|
14
|
-
this.apiClient = apiClient;
|
|
15
|
-
this.debouncer = debouncer;
|
|
16
|
-
this.agentId = agentId;
|
|
17
|
-
this.pollingIntervalMs = pollingIntervalMs;
|
|
18
|
-
this.onHealthy = onHealthy ?? null;
|
|
19
|
-
this.onUnhealthy = onUnhealthy ?? null;
|
|
20
|
-
}
|
|
21
|
-
async start() {
|
|
22
|
-
this.running = true;
|
|
23
|
-
// Initialize: mark current time as baseline (only respond to messages after start)
|
|
24
|
-
const now = Date.now();
|
|
25
|
-
const conversations = await this.apiClient.getConversations();
|
|
26
|
-
for (const convo of conversations) {
|
|
27
|
-
this.lastSeenTimestamps.set(convo.id, now);
|
|
28
|
-
}
|
|
29
|
-
this.onHealthy?.();
|
|
30
|
-
// Start polling
|
|
31
|
-
this.pollTimer = setInterval(() => this.poll(), this.pollingIntervalMs);
|
|
32
|
-
}
|
|
33
|
-
async poll() {
|
|
34
|
-
if (!this.running)
|
|
35
|
-
return;
|
|
36
|
-
try {
|
|
37
|
-
const conversations = await this.apiClient.getConversations();
|
|
38
|
-
this.onHealthy?.();
|
|
39
|
-
const activeConvos = this.findActiveConversations(conversations);
|
|
40
|
-
await Promise.all(activeConvos.map(async (convo) => {
|
|
41
|
-
try {
|
|
42
|
-
const page = await this.apiClient.getMessagesPage(convo.id, 50);
|
|
43
|
-
const messages = page.messages;
|
|
44
|
-
const participationHistory = buildParticipationHistorySnapshots(messages, this.agentId);
|
|
45
|
-
// Filter to only new messages (after lastSeen, not from self)
|
|
46
|
-
const lastSeen = this.lastSeenTimestamps.get(convo.id) || 0;
|
|
47
|
-
const newMessages = messages.filter((m) => {
|
|
48
|
-
const msgTime = new Date(m.createdAt).getTime();
|
|
49
|
-
return msgTime > lastSeen && m.senderId !== this.agentId;
|
|
50
|
-
});
|
|
51
|
-
const dispatchable = await Promise.all(newMessages.map(async (message) => ({
|
|
52
|
-
message,
|
|
53
|
-
allow: await shouldDispatchInboundMessage(convo.id, this.agentId, message, {
|
|
54
|
-
conversationType: convo.type,
|
|
55
|
-
behavior: page.behavior,
|
|
56
|
-
recentHumanCount: participationHistory.get(message.id)?.recentHumanCount,
|
|
57
|
-
consecutiveAgentTurns: participationHistory.get(message.id)?.consecutiveAgentTurns,
|
|
58
|
-
currentAgentStreakStartedByHuman: participationHistory.get(message.id)?.currentAgentStreakStartedByHuman,
|
|
59
|
-
}),
|
|
60
|
-
})));
|
|
61
|
-
for (const msg of dispatchable.filter((entry) => entry.allow).map((entry) => entry.message)) {
|
|
62
|
-
this.debouncer.add(convo.id, msg);
|
|
63
|
-
}
|
|
64
|
-
// Update lastSeen to latest message timestamp
|
|
65
|
-
if (messages.length > 0) {
|
|
66
|
-
const latestTime = Math.max(...messages.map((m) => new Date(m.createdAt).getTime()));
|
|
67
|
-
this.lastSeenTimestamps.set(convo.id, latestTime);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
console.error(`[canon-sdk] Failed to fetch messages for ${convo.id}:`, err);
|
|
72
|
-
}
|
|
73
|
-
}));
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
this.onUnhealthy?.();
|
|
77
|
-
console.error('[canon-sdk] Polling error:', err);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
findActiveConversations(conversations) {
|
|
81
|
-
return conversations.filter((convo) => {
|
|
82
|
-
if (!convo.lastMessage)
|
|
83
|
-
return false;
|
|
84
|
-
// Skip if agent was last sender
|
|
85
|
-
if (convo.lastMessage.senderId === this.agentId)
|
|
86
|
-
return false;
|
|
87
|
-
const lastMsgTime = new Date(convo.lastMessage.timestamp).getTime();
|
|
88
|
-
const lastSeen = this.lastSeenTimestamps.get(convo.id) || 0;
|
|
89
|
-
return lastMsgTime > lastSeen;
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
stop() {
|
|
93
|
-
this.running = false;
|
|
94
|
-
if (this.pollTimer) {
|
|
95
|
-
clearInterval(this.pollTimer);
|
|
96
|
-
this.pollTimer = null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|