@canonmsg/agent-sdk 7.1.3 → 8.1.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/dist/auth.d.ts +0 -6
- package/dist/auth.js +0 -19
- package/dist/canon-agent.d.ts +11 -2
- package/dist/canon-agent.js +18 -52
- package/dist/index.d.ts +1 -1
- package/dist/realtime.d.ts +2 -4
- package/dist/realtime.js +3 -15
- package/dist/runtime-card.d.ts +19 -0
- package/dist/runtime-card.js +42 -0
- package/dist/session-manager.d.ts +0 -2
- package/dist/session-manager.js +0 -4
- package/package.json +2 -2
- package/dist/policy-history.d.ts +0 -10
- package/dist/policy-history.js +0 -11
- package/dist/turn-filter.d.ts +0 -9
- package/dist/turn-filter.js +0 -25
package/dist/auth.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { CanonClient } from '@canonmsg/core';
|
|
2
2
|
export declare class AuthManager {
|
|
3
3
|
private apiClient;
|
|
4
|
-
private token;
|
|
5
|
-
private agentId;
|
|
6
4
|
private expiresAt;
|
|
7
5
|
private refreshTimer;
|
|
8
|
-
private onRefreshCallback;
|
|
9
6
|
private refreshRetryCount;
|
|
10
7
|
constructor(apiClient: CanonClient);
|
|
11
8
|
authenticate(): Promise<{
|
|
@@ -15,8 +12,5 @@ export declare class AuthManager {
|
|
|
15
12
|
private scheduleRefresh;
|
|
16
13
|
/** Retry with exponential backoff (30s -> 60s -> 120s -> 240s cap, max 10 attempts) */
|
|
17
14
|
private scheduleRetry;
|
|
18
|
-
setOnRefresh(cb: (token: string) => void): void;
|
|
19
|
-
getToken(): string | null;
|
|
20
|
-
getAgentId(): string | null;
|
|
21
15
|
destroy(): void;
|
|
22
16
|
}
|
package/dist/auth.js
CHANGED
|
@@ -3,19 +3,14 @@ const BASE_RETRY_MS = 30_000;
|
|
|
3
3
|
const MAX_RETRY_BACKOFF_MS = 240_000;
|
|
4
4
|
export class AuthManager {
|
|
5
5
|
apiClient;
|
|
6
|
-
token = null;
|
|
7
|
-
agentId = null;
|
|
8
6
|
expiresAt = 0;
|
|
9
7
|
refreshTimer = null;
|
|
10
|
-
onRefreshCallback = null;
|
|
11
8
|
refreshRetryCount = 0;
|
|
12
9
|
constructor(apiClient) {
|
|
13
10
|
this.apiClient = apiClient;
|
|
14
11
|
}
|
|
15
12
|
async authenticate() {
|
|
16
13
|
const result = await this.apiClient.getAuthToken();
|
|
17
|
-
this.token = result.token;
|
|
18
|
-
this.agentId = result.agentId;
|
|
19
14
|
this.expiresAt = new Date(result.expiresAt).getTime();
|
|
20
15
|
this.refreshRetryCount = 0;
|
|
21
16
|
this.scheduleRefresh();
|
|
@@ -29,12 +24,9 @@ export class AuthManager {
|
|
|
29
24
|
this.refreshTimer = setTimeout(async () => {
|
|
30
25
|
try {
|
|
31
26
|
const result = await this.apiClient.getAuthToken();
|
|
32
|
-
this.token = result.token;
|
|
33
27
|
this.expiresAt = new Date(result.expiresAt).getTime();
|
|
34
28
|
this.refreshRetryCount = 0;
|
|
35
29
|
this.scheduleRefresh();
|
|
36
|
-
if (this.onRefreshCallback)
|
|
37
|
-
this.onRefreshCallback(result.token);
|
|
38
30
|
}
|
|
39
31
|
catch (err) {
|
|
40
32
|
console.error('[canon-sdk] Token refresh failed:', err);
|
|
@@ -53,21 +45,10 @@ export class AuthManager {
|
|
|
53
45
|
console.warn(`[canon-sdk] Retrying token refresh in ${backoff / 1000}s (attempt ${this.refreshRetryCount}/${MAX_REFRESH_RETRIES})`);
|
|
54
46
|
this.refreshTimer = setTimeout(() => this.scheduleRefresh(), backoff);
|
|
55
47
|
}
|
|
56
|
-
setOnRefresh(cb) {
|
|
57
|
-
this.onRefreshCallback = cb;
|
|
58
|
-
}
|
|
59
|
-
getToken() {
|
|
60
|
-
return this.token;
|
|
61
|
-
}
|
|
62
|
-
getAgentId() {
|
|
63
|
-
return this.agentId;
|
|
64
|
-
}
|
|
65
48
|
destroy() {
|
|
66
49
|
if (this.refreshTimer) {
|
|
67
50
|
clearTimeout(this.refreshTimer);
|
|
68
51
|
this.refreshTimer = null;
|
|
69
52
|
}
|
|
70
|
-
this.token = null;
|
|
71
|
-
this.agentId = null;
|
|
72
53
|
}
|
|
73
54
|
}
|
package/dist/canon-agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AddMemberResult, type CanonContact, type CanonConversation, type CreateConversationResult, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult, type CanonVoiceSession, type CanonVoiceSessionToken, type CreateVoiceSessionOptions, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
1
|
+
import { type AddMemberResult, type CanonContact, type CanonConversation, type CanonConversationsPage, type CanonConversationsPageOptions, type CreateConversationResult, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult, type CanonVoiceSession, type CanonVoiceSessionToken, type CreateVoiceSessionOptions, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
2
2
|
import type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, MessageUpdatedHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Contact-graph operations exposed under `agent.contacts`. Wraps the REST
|
|
@@ -22,6 +22,15 @@ export interface AgentConversationsAPI {
|
|
|
22
22
|
list(options?: {
|
|
23
23
|
targetUserId?: string;
|
|
24
24
|
}): Promise<CanonConversation[]>;
|
|
25
|
+
/**
|
|
26
|
+
* One page of conversations plus the cursor to continue from, for agents
|
|
27
|
+
* with enough conversations that fetching all of them on every poll is
|
|
28
|
+
* wasteful. Continue with `before: page.nextBefore` until it is null.
|
|
29
|
+
*
|
|
30
|
+
* Kept separate from `list` on purpose: `list` filters by `targetUserId`
|
|
31
|
+
* over the complete set, which cannot be honored a page at a time.
|
|
32
|
+
*/
|
|
33
|
+
page(options?: CanonConversationsPageOptions): Promise<CanonConversationsPage>;
|
|
25
34
|
}
|
|
26
35
|
export declare class CanonAgent {
|
|
27
36
|
private options;
|
|
@@ -103,7 +112,7 @@ export declare class CanonAgent {
|
|
|
103
112
|
on(event: 'callStarted', handler: (payload: VoiceSessionEventPayload) => void | Promise<void>): void;
|
|
104
113
|
on(event: 'callEnded', handler: (payload: VoiceSessionEventPayload) => void | Promise<void>): void;
|
|
105
114
|
onPrimitive(primitive: CanonRuntimePrimitiveId | '*', handler: RuntimePrimitiveHandler): void;
|
|
106
|
-
describeCommands(
|
|
115
|
+
describeCommands(): ReadonlyArray<CanonRuntimeCommandDescriptor>;
|
|
107
116
|
publishRuntimeFacts(conversationId: string, facts: ReadonlyArray<CanonRuntimeFact>): Promise<void>;
|
|
108
117
|
publishRuntimeActivity(conversationId: string, item: CanonRuntimeActivityItem): Promise<void>;
|
|
109
118
|
clearRuntimeActivity(conversationId: string, options?: ClearRuntimeActivityOptions): Promise<void>;
|
package/dist/canon-agent.js
CHANGED
|
@@ -2,15 +2,13 @@ import { ApprovalManager, RuntimeRequestManager, runtimeInputDescriptor, runtime
|
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { AuthManager } from './auth.js';
|
|
4
4
|
import { Debouncer } from './debouncer.js';
|
|
5
|
-
import { buildRuntimeCardCreateArgs } from './runtime-card.js';
|
|
5
|
+
import { DEFAULT_RUNTIME_INPUT_TIMEOUT_MS, RUNTIME_INPUT_ID_PATTERN, buildRuntimeCardCreateArgs, normalizeResponseUserId, resolveRuntimeCardRouting, } from './runtime-card.js';
|
|
6
6
|
import { materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, uploadMediaFile, } from './media.js';
|
|
7
7
|
import { SessionManager } from './session-manager.js';
|
|
8
8
|
const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
|
|
9
9
|
const RUNTIME_CONTROL_POLL_INTERVAL_MS = 2_000;
|
|
10
10
|
const RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
|
|
11
11
|
const RUNTIME_PRIMITIVE_DEDUPE_MAX = 1_000;
|
|
12
|
-
const DEFAULT_RUNTIME_INPUT_TIMEOUT_MS = 5 * 60_000;
|
|
13
|
-
const RUNTIME_INPUT_ID_PATTERN = /^[A-Za-z0-9_.:-]{1,160}$/;
|
|
14
12
|
const SDK_MESSAGE_ID_READABLE_MAX = 120;
|
|
15
13
|
/** Canon's message id ceiling, matching core's chunked sender. */
|
|
16
14
|
const CANON_MESSAGE_ID_MAX = 160;
|
|
@@ -163,16 +161,6 @@ function safeRuntimeInputId(value, kind) {
|
|
|
163
161
|
? normalized
|
|
164
162
|
: `${kind}_${randomUUID()}`;
|
|
165
163
|
}
|
|
166
|
-
function safeRuntimeCardId(value) {
|
|
167
|
-
const raw = value?.trim() || `card_${randomUUID()}`;
|
|
168
|
-
const normalized = raw.replace(/[.#$\[\]\/\s]+/g, '_').slice(0, 80);
|
|
169
|
-
return RUNTIME_INPUT_ID_PATTERN.test(normalized)
|
|
170
|
-
? normalized
|
|
171
|
-
: `card_${randomUUID()}`;
|
|
172
|
-
}
|
|
173
|
-
function normalizeResponseUserId(value) {
|
|
174
|
-
return value?.trim() || undefined;
|
|
175
|
-
}
|
|
176
164
|
/**
|
|
177
165
|
* Part id for text this sdk split itself, mirroring the `-part-N` rule (and the
|
|
178
166
|
* 160-character id cap) that core's chunked sender applies to a split final, so
|
|
@@ -357,6 +345,7 @@ export class CanonAgent {
|
|
|
357
345
|
return conversations;
|
|
358
346
|
return conversations.filter((conversation) => conversation.memberIds.includes(options.targetUserId));
|
|
359
347
|
},
|
|
348
|
+
page: (options) => apiClient.getConversationsPage(options),
|
|
360
349
|
};
|
|
361
350
|
if (options.sessions?.enabled) {
|
|
362
351
|
this.sessionManager = new SessionManager({
|
|
@@ -523,7 +512,7 @@ export class CanonAgent {
|
|
|
523
512
|
}
|
|
524
513
|
void this.publishAgentRuntime().catch(() => { });
|
|
525
514
|
}
|
|
526
|
-
describeCommands(
|
|
515
|
+
describeCommands() {
|
|
527
516
|
return this.buildRuntimeDescriptor().commands ?? [];
|
|
528
517
|
}
|
|
529
518
|
async publishRuntimeFacts(conversationId, facts) {
|
|
@@ -690,7 +679,7 @@ export class CanonAgent {
|
|
|
690
679
|
// 4. Start delivery
|
|
691
680
|
const { RealtimeManager } = await import('./realtime.js');
|
|
692
681
|
this.voiceEventsEnabled = Boolean(this.callStartedHandler || this.callEndedHandler);
|
|
693
|
-
const rtm = new RealtimeManager(this.options.apiKey, this.debouncer, agentId, this.options.streamUrl,
|
|
682
|
+
const rtm = new RealtimeManager(this.options.apiKey, this.debouncer, agentId, this.options.streamUrl, { enableVoiceEvents: this.voiceEventsEnabled });
|
|
694
683
|
if (this.voiceEventsEnabled) {
|
|
695
684
|
rtm.setCallHandlers({
|
|
696
685
|
onCallStarted: (payload) => {
|
|
@@ -1208,11 +1197,15 @@ export class CanonAgent {
|
|
|
1208
1197
|
createRuntimeStatePublisher() {
|
|
1209
1198
|
if (!this.agentId)
|
|
1210
1199
|
return null;
|
|
1200
|
+
// start() assigns rtdbHandle before agentId, so this can only be null
|
|
1201
|
+
// when the agent has not started — same condition as the guard above.
|
|
1202
|
+
if (!this.rtdbHandle)
|
|
1203
|
+
return null;
|
|
1211
1204
|
return createRuntimeStatePublisher({
|
|
1212
1205
|
agentId: this.agentId,
|
|
1213
1206
|
clientType: this.options.clientType ?? 'generic',
|
|
1214
1207
|
hostMode: this.options.runtimeControlSurface === 'host',
|
|
1215
|
-
|
|
1208
|
+
rtdb: this.rtdbHandle,
|
|
1216
1209
|
});
|
|
1217
1210
|
}
|
|
1218
1211
|
requireRuntimeStatePublisher() {
|
|
@@ -1566,7 +1559,7 @@ export class CanonAgent {
|
|
|
1566
1559
|
agent,
|
|
1567
1560
|
membershipChange,
|
|
1568
1561
|
});
|
|
1569
|
-
const participationHistory = buildParticipationHistorySnapshot(history
|
|
1562
|
+
const participationHistory = buildParticipationHistorySnapshot(history);
|
|
1570
1563
|
const turnContext = buildCanonTurnContextV2({
|
|
1571
1564
|
content: latestMessage ? renderCanonHostInboundContent(latestMessage) : '[Empty message]',
|
|
1572
1565
|
conversationId,
|
|
@@ -1694,7 +1687,6 @@ export class CanonAgent {
|
|
|
1694
1687
|
const inputId = safeRuntimeInputId(request.inputId, request.kind);
|
|
1695
1688
|
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1696
1689
|
const expiresAtMs = Date.now() + timeoutMs;
|
|
1697
|
-
const expiresAt = new Date(expiresAtMs).toISOString();
|
|
1698
1690
|
let result = { status: 'timeout', inputId };
|
|
1699
1691
|
let requestCreated = false;
|
|
1700
1692
|
const ownerOnly = request.kind === 'secret'
|
|
@@ -1799,23 +1791,10 @@ export class CanonAgent {
|
|
|
1799
1791
|
};
|
|
1800
1792
|
const sendCard = async (request) => {
|
|
1801
1793
|
throwIfAborted();
|
|
1802
|
-
const cardId =
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
? request.expiresAt
|
|
1807
|
-
: typeof request.expiresAt === 'string'
|
|
1808
|
-
? Date.parse(request.expiresAt)
|
|
1809
|
-
: null;
|
|
1810
|
-
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1811
|
-
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1812
|
-
? explicitExpiresAt
|
|
1813
|
-
: Date.now() + timeoutMs;
|
|
1814
|
-
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1815
|
-
?? triggeringHumanId;
|
|
1816
|
-
const routedRequest = responseUserId
|
|
1817
|
-
? { ...request, responseUserId }
|
|
1818
|
-
: request;
|
|
1794
|
+
const { cardId, expiresAtMs, routedRequest } = resolveRuntimeCardRouting({
|
|
1795
|
+
request,
|
|
1796
|
+
triggeringHumanId,
|
|
1797
|
+
});
|
|
1819
1798
|
// Fire-and-forget: post the durable card and return. The backend treats an
|
|
1820
1799
|
// action-less card as display (no pending state, no response expected).
|
|
1821
1800
|
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
@@ -1844,23 +1823,10 @@ export class CanonAgent {
|
|
|
1844
1823
|
&& request.card.blocks.some((block) => block.kind === 'actions');
|
|
1845
1824
|
if (!hasActions)
|
|
1846
1825
|
return sendCard(request);
|
|
1847
|
-
const cardId =
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
? request.expiresAt
|
|
1852
|
-
: typeof request.expiresAt === 'string'
|
|
1853
|
-
? Date.parse(request.expiresAt)
|
|
1854
|
-
: null;
|
|
1855
|
-
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1856
|
-
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1857
|
-
? explicitExpiresAt
|
|
1858
|
-
: Date.now() + timeoutMs;
|
|
1859
|
-
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1860
|
-
?? triggeringHumanId;
|
|
1861
|
-
const routedRequest = responseUserId
|
|
1862
|
-
? { ...request, responseUserId }
|
|
1863
|
-
: request;
|
|
1826
|
+
const { cardId, expiresAtMs, routedRequest } = resolveRuntimeCardRouting({
|
|
1827
|
+
request,
|
|
1828
|
+
triggeringHumanId,
|
|
1829
|
+
});
|
|
1864
1830
|
let result = { status: 'timeout', cardId };
|
|
1865
1831
|
let requestCreated = false;
|
|
1866
1832
|
let requestResolved = false;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export { SessionManager } from './session-manager.js';
|
|
|
6
6
|
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
|
7
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, CanonReplyContext, CanonSelfContext, CanonTurnContextV2, CanonRuntimeDescriptor, MessageUpdatedPayload, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, CreateConversationResult, DirectSessionSelection, } from '@canonmsg/core';
|
|
9
|
+
export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, CanonMessage, CanonConversation, CanonConversationsPage, CanonConversationsPageOptions, CanonReplyContext, CanonSelfContext, CanonTurnContextV2, CanonRuntimeDescriptor, MessageUpdatedPayload, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, CreateConversationResult, DirectSessionSelection, } from '@canonmsg/core';
|
|
10
10
|
export type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, FinalMessageResult, MessageHandler, MessageHandlerContext, MessageUpdatedHandler, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimeApprovalRequest, RuntimeInputRequest, RuntimeInputResult, RuntimeControlSurface, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentContext, type
|
|
1
|
+
import { type AgentContext, type ContactAddedPayload, type ContactApprovedPayload, type ContactRemovedPayload, type ContactRequestPayload, type ConversationUpdatedPayload, type MessageUpdatedPayload, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
2
2
|
import { Debouncer } from './debouncer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Wraps @canonmsg/core's CanonStream with SDK-specific features:
|
|
@@ -8,9 +8,7 @@ import { Debouncer } from './debouncer.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class RealtimeManager {
|
|
10
10
|
private debouncer;
|
|
11
|
-
private agentId;
|
|
12
11
|
private stream;
|
|
13
|
-
private running;
|
|
14
12
|
/** Recent inbound message IDs (`conversationId:messageId`) for cross-flush dedupe. */
|
|
15
13
|
private readonly recentInboundMessageIds;
|
|
16
14
|
private lastSseErrorKey;
|
|
@@ -28,7 +26,7 @@ export declare class RealtimeManager {
|
|
|
28
26
|
private onDisconnected;
|
|
29
27
|
private onCallStarted;
|
|
30
28
|
private onCallEnded;
|
|
31
|
-
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string,
|
|
29
|
+
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, options?: {
|
|
32
30
|
enableVoiceEvents?: boolean;
|
|
33
31
|
});
|
|
34
32
|
private hasSeenInboundMessage;
|
package/dist/realtime.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { CanonStream, } from '@canonmsg/core';
|
|
2
2
|
const RECENT_INBOUND_TTL_MS = 30 * 60 * 1000;
|
|
3
3
|
const MAX_RECENT_INBOUND_MESSAGE_IDS = 5000;
|
|
4
|
-
function messageCreatedAtMs(createdAt) {
|
|
5
|
-
if (!createdAt)
|
|
6
|
-
return 0;
|
|
7
|
-
const parsed = new Date(createdAt).getTime();
|
|
8
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
9
|
-
}
|
|
10
4
|
/**
|
|
11
5
|
* Wraps @canonmsg/core's CanonStream with SDK-specific features:
|
|
12
6
|
* - Debouncer integration (message batching)
|
|
@@ -15,9 +9,7 @@ function messageCreatedAtMs(createdAt) {
|
|
|
15
9
|
*/
|
|
16
10
|
export class RealtimeManager {
|
|
17
11
|
debouncer;
|
|
18
|
-
agentId;
|
|
19
12
|
stream;
|
|
20
|
-
running = false;
|
|
21
13
|
/** Recent inbound message IDs (`conversationId:messageId`) for cross-flush dedupe. */
|
|
22
14
|
recentInboundMessageIds = new Map();
|
|
23
15
|
lastSseErrorKey = null;
|
|
@@ -35,9 +27,8 @@ export class RealtimeManager {
|
|
|
35
27
|
onDisconnected = null;
|
|
36
28
|
onCallStarted = null;
|
|
37
29
|
onCallEnded = null;
|
|
38
|
-
constructor(apiKey, debouncer, agentId, streamUrl,
|
|
30
|
+
constructor(apiKey, debouncer, agentId, streamUrl, options) {
|
|
39
31
|
this.debouncer = debouncer;
|
|
40
|
-
this.agentId = agentId;
|
|
41
32
|
this.stream = new CanonStream({
|
|
42
33
|
apiKey,
|
|
43
34
|
agentId,
|
|
@@ -64,7 +55,7 @@ export class RealtimeManager {
|
|
|
64
55
|
if (this.hasSeenInboundMessage(payload.conversationId, payload.message.id)) {
|
|
65
56
|
return;
|
|
66
57
|
}
|
|
67
|
-
this.recordSeenInboundMessage(payload.conversationId, payload.message.id
|
|
58
|
+
this.recordSeenInboundMessage(payload.conversationId, payload.message.id);
|
|
68
59
|
if (payload.turnDispatch && payload.turnDispatch.kind !== 'run_turn') {
|
|
69
60
|
console.error(`[canon-sdk] Ignoring server-dispatched observe-only message in ${payload.conversationId}: ${payload.turnDispatch.reason}`);
|
|
70
61
|
return;
|
|
@@ -140,10 +131,9 @@ export class RealtimeManager {
|
|
|
140
131
|
hasSeenInboundMessage(conversationId, messageId) {
|
|
141
132
|
return this.recentInboundMessageIds.has(`${conversationId}:${messageId}`);
|
|
142
133
|
}
|
|
143
|
-
recordSeenInboundMessage(conversationId, messageId
|
|
134
|
+
recordSeenInboundMessage(conversationId, messageId) {
|
|
144
135
|
const now = Date.now();
|
|
145
136
|
this.recentInboundMessageIds.set(`${conversationId}:${messageId}`, now);
|
|
146
|
-
void createdAtMs;
|
|
147
137
|
this.pruneRecentInboundMessageIds(now);
|
|
148
138
|
}
|
|
149
139
|
pruneRecentInboundMessageIds(now = Date.now()) {
|
|
@@ -205,11 +195,9 @@ export class RealtimeManager {
|
|
|
205
195
|
this.onCallEnded = handlers.onCallEnded ?? null;
|
|
206
196
|
}
|
|
207
197
|
async start() {
|
|
208
|
-
this.running = true;
|
|
209
198
|
await this.stream.start();
|
|
210
199
|
}
|
|
211
200
|
stop() {
|
|
212
|
-
this.running = false;
|
|
213
201
|
this.stream.stop();
|
|
214
202
|
}
|
|
215
203
|
}
|
package/dist/runtime-card.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { RuntimeCardNativeMetadata, RuntimeCardV1 } from '@canonmsg/core';
|
|
2
2
|
import type { RuntimeCardRequest } from './types';
|
|
3
|
+
export declare const DEFAULT_RUNTIME_INPUT_TIMEOUT_MS: number;
|
|
4
|
+
export declare const RUNTIME_INPUT_ID_PATTERN: RegExp;
|
|
5
|
+
export declare function normalizeResponseUserId(value: string | undefined): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the routing prelude shared verbatim by `sendCard` (display) and
|
|
8
|
+
* `requestCard` (interactive): the sanitized card id, the effective expiry,
|
|
9
|
+
* and the request with an explicit responder folded in.
|
|
10
|
+
*
|
|
11
|
+
* `triggeringHumanId` must be passed in by the caller rather than re-derived
|
|
12
|
+
* here — it is captured from the enclosing message handler's scope.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveRuntimeCardRouting(input: {
|
|
15
|
+
request: RuntimeCardRequest;
|
|
16
|
+
triggeringHumanId?: string;
|
|
17
|
+
}): {
|
|
18
|
+
cardId: string;
|
|
19
|
+
expiresAtMs: number;
|
|
20
|
+
routedRequest: RuntimeCardRequest;
|
|
21
|
+
};
|
|
3
22
|
/** Arguments passed to `CanonClient.createRuntimeCardRequest`. */
|
|
4
23
|
export interface RuntimeCardCreateArgs {
|
|
5
24
|
conversationId: string;
|
package/dist/runtime-card.js
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export const DEFAULT_RUNTIME_INPUT_TIMEOUT_MS = 5 * 60_000;
|
|
3
|
+
export const RUNTIME_INPUT_ID_PATTERN = /^[A-Za-z0-9_.:-]{1,160}$/;
|
|
4
|
+
function safeRuntimeCardId(value) {
|
|
5
|
+
const raw = value?.trim() || `card_${randomUUID()}`;
|
|
6
|
+
const normalized = raw.replace(/[.#$\[\]\/\s]+/g, '_').slice(0, 80);
|
|
7
|
+
return RUNTIME_INPUT_ID_PATTERN.test(normalized)
|
|
8
|
+
? normalized
|
|
9
|
+
: `card_${randomUUID()}`;
|
|
10
|
+
}
|
|
11
|
+
export function normalizeResponseUserId(value) {
|
|
12
|
+
return value?.trim() || undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the routing prelude shared verbatim by `sendCard` (display) and
|
|
16
|
+
* `requestCard` (interactive): the sanitized card id, the effective expiry,
|
|
17
|
+
* and the request with an explicit responder folded in.
|
|
18
|
+
*
|
|
19
|
+
* `triggeringHumanId` must be passed in by the caller rather than re-derived
|
|
20
|
+
* here — it is captured from the enclosing message handler's scope.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveRuntimeCardRouting(input) {
|
|
23
|
+
const { request, triggeringHumanId } = input;
|
|
24
|
+
const cardId = safeRuntimeCardId(request.cardId ?? request.card.cardId);
|
|
25
|
+
const explicitExpiresAt = request.expiresAt instanceof Date
|
|
26
|
+
? request.expiresAt.getTime()
|
|
27
|
+
: typeof request.expiresAt === 'number'
|
|
28
|
+
? request.expiresAt
|
|
29
|
+
: typeof request.expiresAt === 'string'
|
|
30
|
+
? Date.parse(request.expiresAt)
|
|
31
|
+
: null;
|
|
32
|
+
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
33
|
+
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
34
|
+
? explicitExpiresAt
|
|
35
|
+
: Date.now() + timeoutMs;
|
|
36
|
+
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
37
|
+
?? triggeringHumanId;
|
|
38
|
+
const routedRequest = responseUserId
|
|
39
|
+
? { ...request, responseUserId }
|
|
40
|
+
: request;
|
|
41
|
+
return { cardId, expiresAtMs, routedRequest };
|
|
42
|
+
}
|
|
1
43
|
/**
|
|
2
44
|
* Build the `createRuntimeCardRequest` payload shared by `sendCard` (display)
|
|
3
45
|
* and `requestCard` (interactive).
|
|
@@ -59,8 +59,6 @@ export declare class SessionManager {
|
|
|
59
59
|
seedHistory(conversationId: string, history: CanonMessage[]): void;
|
|
60
60
|
/** Remove idle sessions */
|
|
61
61
|
private sweep;
|
|
62
|
-
/** Number of active sessions */
|
|
63
|
-
get sessionCount(): number;
|
|
64
62
|
/** Number of queued batches waiting behind the active turn for this conversation. */
|
|
65
63
|
getQueueDepth(conversationId: string): number;
|
|
66
64
|
/** Drop one not-yet-running queued message for a conversation. */
|
package/dist/session-manager.js
CHANGED
|
@@ -191,10 +191,6 @@ export class SessionManager {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
-
/** Number of active sessions */
|
|
195
|
-
get sessionCount() {
|
|
196
|
-
return this.sessions.size;
|
|
197
|
-
}
|
|
198
194
|
/** Number of queued batches waiting behind the active turn for this conversation. */
|
|
199
195
|
getQueueDepth(conversationId) {
|
|
200
196
|
return this.queues.get(conversationId)?.length ?? 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.1.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": "^
|
|
31
|
+
"@canonmsg/core": "^9.2.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
package/dist/policy-history.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type CanonMessage, type ParticipationHistorySnapshot } from '@canonmsg/core';
|
|
2
|
-
export type { ParticipationHistorySnapshot } from '@canonmsg/core';
|
|
3
|
-
/**
|
|
4
|
-
* Builds message-specific participation history snapshots for backlog delivery.
|
|
5
|
-
*
|
|
6
|
-
* `messages` must be ordered newest-first, matching Canon's `getMessages()`
|
|
7
|
-
* API. Each snapshot is computed from older history only, never from the
|
|
8
|
-
* target message itself or newer messages that had not occurred yet.
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildParticipationHistorySnapshots(messages: CanonMessage[], agentId: string): Map<string, ParticipationHistorySnapshot>;
|
package/dist/policy-history.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { buildParticipationHistorySnapshots as buildSharedParticipationHistorySnapshots, } from '@canonmsg/core';
|
|
2
|
-
/**
|
|
3
|
-
* Builds message-specific participation history snapshots for backlog delivery.
|
|
4
|
-
*
|
|
5
|
-
* `messages` must be ordered newest-first, matching Canon's `getMessages()`
|
|
6
|
-
* API. Each snapshot is computed from older history only, never from the
|
|
7
|
-
* target message itself or newer messages that had not occurred yet.
|
|
8
|
-
*/
|
|
9
|
-
export function buildParticipationHistorySnapshots(messages, agentId) {
|
|
10
|
-
return buildSharedParticipationHistorySnapshots(messages, agentId);
|
|
11
|
-
}
|
package/dist/turn-filter.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type ResolvedAgentBehaviorPolicy, type CanonMessage, type MessageCreatedPayload } from '@canonmsg/core';
|
|
2
|
-
export declare function shouldDispatchInboundMessage(_conversationId: string, agentId: string, message: CanonMessage, options?: {
|
|
3
|
-
conversationType?: 'direct' | 'group' | 'unknown';
|
|
4
|
-
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
5
|
-
recentHumanCount?: number;
|
|
6
|
-
consecutiveAgentTurns?: number;
|
|
7
|
-
currentAgentStreakStartedByHuman?: boolean;
|
|
8
|
-
turnDispatch?: MessageCreatedPayload['turnDispatch'];
|
|
9
|
-
}): Promise<boolean>;
|
package/dist/turn-filter.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { evaluateParticipationPolicy, shouldTriggerAgentTurn, } from '@canonmsg/core';
|
|
2
|
-
export async function shouldDispatchInboundMessage(_conversationId, agentId, message, options) {
|
|
3
|
-
if (message.senderId === agentId)
|
|
4
|
-
return false;
|
|
5
|
-
if (options?.turnDispatch) {
|
|
6
|
-
return options.turnDispatch.kind === 'run_turn';
|
|
7
|
-
}
|
|
8
|
-
const triggerDecision = shouldTriggerAgentTurn({
|
|
9
|
-
senderType: message.senderType,
|
|
10
|
-
metadata: message.metadata,
|
|
11
|
-
});
|
|
12
|
-
if (!triggerDecision.allow)
|
|
13
|
-
return false;
|
|
14
|
-
if (!options?.behavior)
|
|
15
|
-
return true;
|
|
16
|
-
return evaluateParticipationPolicy(options.behavior, {
|
|
17
|
-
conversationType: options.conversationType ?? 'unknown',
|
|
18
|
-
senderType: message.senderType,
|
|
19
|
-
isOwner: message.isOwner,
|
|
20
|
-
mentionedAgent: Array.isArray(message.mentions) && message.mentions.includes(agentId),
|
|
21
|
-
recentHumanCount: options.recentHumanCount,
|
|
22
|
-
consecutiveAgentTurns: options.consecutiveAgentTurns,
|
|
23
|
-
currentAgentStreakStartedByHuman: options.currentAgentStreakStartedByHuman,
|
|
24
|
-
}).allow;
|
|
25
|
-
}
|