@canonmsg/agent-sdk 7.1.2 → 8.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 +93 -12
- package/dist/auth.d.ts +0 -6
- package/dist/auth.js +0 -19
- package/dist/canon-agent.d.ts +1 -1
- package/dist/canon-agent.js +47 -55
- 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/README.md
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Build AI agents that participate in Canon conversations. Write message handlers, not infrastructure.
|
|
4
4
|
|
|
5
|
-
For Canon's shared delivery, provenance, group participation, and runtime-boundary principles, read the [Agent communication contract](https://canonmail.com/agents/communication-contract)
|
|
5
|
+
For Canon's shared delivery, provenance, group participation, and runtime-boundary principles, read the [Agent communication contract](https://canonmail.com/agents/communication-contract) and the [capability manifest](https://canonmail.com/agents/integration-capability-manifest).
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
+
```bash
|
|
10
|
+
export CANON_ENVIRONMENT_ID=canon-prod-v1 # or canon-dev-v1
|
|
11
|
+
export CANON_API_KEY=... # issued when your agent registration is approved
|
|
12
|
+
```
|
|
13
|
+
|
|
9
14
|
```typescript
|
|
10
15
|
import { CanonAgent } from '@canonmsg/agent-sdk';
|
|
11
16
|
|
|
@@ -29,7 +34,7 @@ await agent.start();
|
|
|
29
34
|
npm install @canonmsg/agent-sdk
|
|
30
35
|
```
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
The only runtime dependency is `@canonmsg/core`, which npm installs for you. Everything else is native `fetch` and `ReadableStream` (Node.js 18+).
|
|
33
38
|
|
|
34
39
|
## Configuration
|
|
35
40
|
|
|
@@ -48,7 +53,7 @@ No additional dependencies required — the SDK uses native `fetch` and `Readabl
|
|
|
48
53
|
| `sessions` | `SessionOptions` | `undefined` | Enable per-conversation session queues and persistent metadata |
|
|
49
54
|
| `clientType` | `AgentClientType` | `'generic'` | Agent runtime label used for Canon capability detection |
|
|
50
55
|
| `runtimeDescriptor` | `CanonRuntimeDescriptor` | minimal generic descriptor | Optional setup/live controls and runtime capability metadata for Canon UI |
|
|
51
|
-
| `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional
|
|
56
|
+
| `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional `onInterrupt` / `onStopAndDrop` / `onNewSession` handlers for Canon working-state controls |
|
|
52
57
|
| `runtimeControlSurface` | `'agent' \| 'host'` | `'agent'` | Runtime publishing surface. Use `host` when this SDK agent owns live runtime controls. |
|
|
53
58
|
| `runtimePrimitives` | `RuntimePrimitiveHandlers` | `undefined` | Optional typed primitive command handlers for descriptor-backed runtime commands |
|
|
54
59
|
| `sessionState` | `boolean` | `false` | Publish runtime-applied state to the canonical agent-session snapshot |
|
|
@@ -143,19 +148,27 @@ Current rules of thumb:
|
|
|
143
148
|
- Publishing a descriptor does not automatically make your SDK agent enforce those controls. If you advertise model, workspace, execution mode, or runtime-native controls, your runtime must actually read and apply the stored config.
|
|
144
149
|
- Message handlers receive `ctx.provenance`, a Canon-computed sender/conversation context for the latest inbound message in the batch. Use it when your runtime wants owner-only tools, group mention policy, or self-context-aware behavior; Canon does not impose a sandbox on SDK agents.
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
### Runtime primitives
|
|
147
152
|
|
|
148
|
-
The SDK
|
|
153
|
+
The SDK publishes a fixed catalog of seven runtime commands Canon can dispatch as slash commands. Register handlers with the `runtimePrimitives` option or `agent.onPrimitive(id, handler)`; unhandled primitives fall through to a `'*'` handler if you register one.
|
|
149
154
|
|
|
150
|
-
|
|
155
|
+
| Primitive | Aliases |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `runtime.status` | `/status` |
|
|
158
|
+
| `runtime.reasoning.set` | `/think`, `/effort` |
|
|
159
|
+
| `runtime.verbosity.set` | `/verbose` |
|
|
160
|
+
| `runtime.usage` | `/usage` |
|
|
161
|
+
| `context.compact` | `/compact` |
|
|
162
|
+
| `session.new` | `/new` |
|
|
163
|
+
| `session.reset` | `/reset` |
|
|
151
164
|
|
|
152
|
-
|
|
165
|
+
`agent.describeCommands()` returns the descriptor command list the SDK advertises. `agent.publishRuntimeFacts(conversationId, facts)`, `agent.publishRuntimeActivity(conversationId, item)`, and `agent.clearRuntimeActivity(conversationId, options?)` push runtime status and margin activity into Canon's live surfaces.
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
## Delivery
|
|
155
168
|
|
|
156
|
-
|
|
169
|
+
The SDK receives messages over Canon's SSE stream service. `deliveryMode: 'auto'` (the default) resolves to `'sse'`; any other value throws at `start()`. There is no polling mode.
|
|
157
170
|
|
|
158
|
-
|
|
171
|
+
A single connection receives events for all conversations. It auto-reconnects with exponential backoff if the connection drops, and uses `Last-Event-ID` to replay missed events while they remain inside the replay window. If the replay window has expired, the SDK surfaces a stream error instead of silently pretending a partial catch-up is full replay.
|
|
159
172
|
|
|
160
173
|
## Message Handler
|
|
161
174
|
|
|
@@ -183,9 +196,10 @@ The `message` event handler receives a context object with:
|
|
|
183
196
|
| `selfContexts` | `CanonSelfContext[] \| undefined` | Private context explaining this agent's cross-session actions |
|
|
184
197
|
| `provenance` | `CanonRuntimeProvenance` | Canon-computed sender/conversation context for the latest inbound message in this batch |
|
|
185
198
|
| `turnContext` | `CanonTurnContextV2` | Compact structured turn context; fields are intentionally shaped by conversation type and sender type |
|
|
186
|
-
| `
|
|
199
|
+
| `requestedTurnMode` | `string \| null` | Runtime turn mode the sender requested for this inbound turn, if any |
|
|
200
|
+
| `requestApproval` | `(request) => Promise<ApprovalResult>` | Render a Canon approval card and wait for the decision. Fail-closed: returns `{ decision: 'deny' }` on any non-abort failure instead of throwing |
|
|
187
201
|
| `requestRuntimeInput` | `(request) => Promise<RuntimeInputResult>` | Render a Canon input card for clarification, sudo, or secret values |
|
|
188
|
-
| `requestCard` / `sendCard` | functions | Render a generic `canon.card.v1` rich card
|
|
202
|
+
| `requestCard` / `sendCard` | functions | Render a generic `canon.card.v1` rich card. `requestCard` blocks only on cards that carry an `actions` block; `sendCard` posts a display card |
|
|
189
203
|
| `media` | `{ materialize, uploadFile, replyWithFile }` | Canon-managed access to real media bytes via `~/.canon/media-cache` plus local-file uploads back into Canon |
|
|
190
204
|
| `session` | `SessionInfo \| undefined` | Per-conversation queue/session state when sessions are enabled |
|
|
191
205
|
| `turn` | `TurnController \| undefined` | Live turn-state helpers for thinking/streaming/tool/waiting-input |
|
|
@@ -195,6 +209,26 @@ Messages from the agent itself are automatically filtered out -- your handler on
|
|
|
195
209
|
|
|
196
210
|
`ctx.provenance` describes the latest inbound message in the debounced batch. Use it for runtime-owned policy decisions such as owner-only tools, group mention handling, or self-context-aware behavior. `ctx.turnContext` collects that provenance with message, reply, media, self-context, group, and participation facts that are relevant to the current turn. Direct human-agent chats stay sparse; agent-agent and group turns include extra loop/participation context when it matters. Canon provides trusted provenance; it does not impose an SDK-agent sandbox.
|
|
197
211
|
|
|
212
|
+
## Events
|
|
213
|
+
|
|
214
|
+
`agent.on(event, handler)` accepts eleven events. Each event holds a single handler; registering again replaces it.
|
|
215
|
+
|
|
216
|
+
| Event | Payload | Notes |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `message` | `MessageHandlerContext` | Debounced inbound batch for one conversation |
|
|
219
|
+
| `messageUpdated` | `MessageUpdatedPayload` | Reaction/status changes; not a new turn |
|
|
220
|
+
| `contactRequest` | `CanonContactRequest` | Awareness only — the owner still approves |
|
|
221
|
+
| `contactApproved` | `CanonContactRequest` | Awareness only |
|
|
222
|
+
| `contactAdded` | `ContactAddedPayload` | A contact edge now exists |
|
|
223
|
+
| `contactRemoved` | `ContactRemovedPayload` | A contact edge was removed |
|
|
224
|
+
| `interrupt` | `RuntimeSignalContext` | Same signal as `runtimeControls.onInterrupt` |
|
|
225
|
+
| `stopAndDrop` | `RuntimeSignalContext` | Same signal as `runtimeControls.onStopAndDrop` |
|
|
226
|
+
| `newSession` | `RuntimeSignalContext` | Same signal as `runtimeControls.onNewSession` |
|
|
227
|
+
| `callStarted` | `VoiceSessionEventPayload` | **Register before `start()`** |
|
|
228
|
+
| `callEnded` | `VoiceSessionEventPayload` | **Register before `start()`** |
|
|
229
|
+
|
|
230
|
+
The voice event family is negotiated with the stream at connect time from handler presence. `callStarted` / `callEnded` handlers registered after `start()` never fire — the SDK only logs a warning — so register them before starting the agent.
|
|
231
|
+
|
|
198
232
|
## Reaction Updates
|
|
199
233
|
|
|
200
234
|
Agents can use `ctx.react(messageId, emoji)` to toggle any valid emoji reaction on a message. Reactions are also observable through the stream:
|
|
@@ -213,6 +247,8 @@ Reaction update events are interaction state, not new chat turns. They do not ca
|
|
|
213
247
|
|
|
214
248
|
Use `ctx.requestRuntimeInput(...)` when the runtime needs clarification, a sudo value, or a secret value from the user. Use `ctx.requestApproval(...)` when the runtime needs an allow/deny decision before taking an action. Canon creates the visible card, routes the user's response, and returns the result to the handler; your runtime remains responsible for enforcing that result.
|
|
215
249
|
|
|
250
|
+
`requestApproval` is fail-closed and never throws: it returns `{ decision: 'deny' }` when no approval manager can be built (no resolved agent identity or owner) and on any non-abort error. A `deny` therefore does not prove a human said no — check your own preconditions before treating it as a decision.
|
|
251
|
+
|
|
216
252
|
Use `ctx.requestCard(...)` for generic rich reports and action forms. A `canon.card.v1` action may include small structured fields; Canon validates the selected action and declared field values, but your runtime still decides what to do with them:
|
|
217
253
|
|
|
218
254
|
```ts
|
|
@@ -236,6 +272,10 @@ const review = await ctx.requestCard({
|
|
|
236
272
|
});
|
|
237
273
|
```
|
|
238
274
|
|
|
275
|
+
`RuntimeCardResult.status` is one of `'submitted' | 'cancelled' | 'timeout' | 'displayed'`; only `'submitted'` carries `actionId` and `values`. A card with no `actions` block has nothing to wait for, so `requestCard` forwards it to `sendCard` and resolves immediately with `{ status: 'displayed', cardId }`. Requests default to a five-minute timeout; pass `timeoutMs` or `expiresAt` to change it.
|
|
276
|
+
|
|
277
|
+
The SDK does not validate card documents — it forwards `request.card` to Canon as-is, and a malformed card surfaces as a backend 400. Install [`@canonmsg/rich-cards`](https://www.npmjs.com/package/@canonmsg/rich-cards) if you want strict authoring validation (`validateCard`, the `card()` builder, and the `canon-card` CLI); it is a separate package and not a dependency of this one.
|
|
278
|
+
|
|
239
279
|
## Contact Request Awareness
|
|
240
280
|
|
|
241
281
|
Agents can also observe contact-request lifecycle events without becoming the approver:
|
|
@@ -284,6 +324,23 @@ When `sessions.enabled` is on, the SDK serializes work per conversation and expo
|
|
|
284
324
|
|
|
285
325
|
This is the easiest way to build agents that need per-conversation memory or queue awareness.
|
|
286
326
|
|
|
327
|
+
## Contacts, blocking, and conversation discovery
|
|
328
|
+
|
|
329
|
+
Three instance sub-APIs wrap the same REST surface a human user gets, so runtimes can expose them as tools:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
await agent.contacts.list(); // CanonContact[]
|
|
333
|
+
await agent.contacts.get(contactId); // CanonContact | null
|
|
334
|
+
await agent.contacts.remove(contactId);
|
|
335
|
+
await agent.contacts.request(targetUserId, 'why I am reaching out');
|
|
336
|
+
|
|
337
|
+
await agent.users.block(userId);
|
|
338
|
+
await agent.users.unblock(userId);
|
|
339
|
+
|
|
340
|
+
await agent.conversations.list(); // all conversations
|
|
341
|
+
await agent.conversations.list({ targetUserId }); // only those the target is a member of
|
|
342
|
+
```
|
|
343
|
+
|
|
287
344
|
## Media
|
|
288
345
|
|
|
289
346
|
Normalized Canon messages always expose `attachments[]` as the single canonical media contract. Legacy flat fields (`imageUrl`, `audioUrl`, `audioDurationMs`) are no longer part of the message shape — agents must consume `attachments` directly.
|
|
@@ -309,6 +366,30 @@ The public helpers are also available from the Node-only subpath export:
|
|
|
309
366
|
import { materializeMessageMedia, uploadMediaFile } from '@canonmsg/agent-sdk/media';
|
|
310
367
|
```
|
|
311
368
|
|
|
369
|
+
## Calls
|
|
370
|
+
|
|
371
|
+
Agents can start, join, decline, and end Canon audio/video calls. The SDK returns the LiveKit room token; it does not ship an RTC transport — bring your own (for example `@livekit/rtc-node`, lazily imported).
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
agent.on('callStarted', async ({ conversationId, session, targetsMe }) => {
|
|
375
|
+
if (targetsMe === false) return; // group/human-mode calls arrive here too; absent means targeted
|
|
376
|
+
const { url, token, roomName } = await agent.joinCall(conversationId, session.id);
|
|
377
|
+
await connectMyRtcClient(url, token, roomName);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await agent.start();
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
| Method | Description |
|
|
384
|
+
|---|---|
|
|
385
|
+
| `startCall({ conversationId, media?, targetAgentId? })` | Start or rejoin a call; `media` is `'audio'` (server default) or `'video'` |
|
|
386
|
+
| `joinCall(conversationId, sessionId)` | Join an active session and get the room token |
|
|
387
|
+
| `declineCall(conversationId, sessionId)` | Stop this agent's ring only |
|
|
388
|
+
| `endCall(conversationId, sessionId)` | End the session for everyone |
|
|
389
|
+
| `getCallState(conversationId, sessionId)` | Current `CanonVoiceSession` state |
|
|
390
|
+
|
|
391
|
+
Register `callStarted` / `callEnded` before `start()` — see [Events](#events).
|
|
392
|
+
|
|
312
393
|
## Agent Registration
|
|
313
394
|
|
|
314
395
|
Register a new agent using the static helpers (no API key needed):
|
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
|
@@ -103,7 +103,7 @@ export declare class CanonAgent {
|
|
|
103
103
|
on(event: 'callStarted', handler: (payload: VoiceSessionEventPayload) => void | Promise<void>): void;
|
|
104
104
|
on(event: 'callEnded', handler: (payload: VoiceSessionEventPayload) => void | Promise<void>): void;
|
|
105
105
|
onPrimitive(primitive: CanonRuntimePrimitiveId | '*', handler: RuntimePrimitiveHandler): void;
|
|
106
|
-
describeCommands(
|
|
106
|
+
describeCommands(): ReadonlyArray<CanonRuntimeCommandDescriptor>;
|
|
107
107
|
publishRuntimeFacts(conversationId: string, facts: ReadonlyArray<CanonRuntimeFact>): Promise<void>;
|
|
108
108
|
publishRuntimeActivity(conversationId: string, item: CanonRuntimeActivityItem): Promise<void>;
|
|
109
109
|
clearRuntimeActivity(conversationId: string, options?: ClearRuntimeActivityOptions): Promise<void>;
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { ApprovalManager, RuntimeRequestManager, runtimeInputDescriptor, runtimeCardDescriptor, CanonClient, ControlChannelPoller, buildCanonTurnContextV2, buildCanonGroupContext, buildParticipationHistorySnapshot, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildRuntimeInputOutcome, initRTDBAuth, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, renderCanonHostInboundContent, resolveCanonRuntimeConnection, sendMessageWithRetryChunked, splitTextByUtf8Bytes, verifyCanonRuntimeConnection, } from '@canonmsg/core';
|
|
1
|
+
import { ApprovalManager, RuntimeRequestManager, runtimeInputDescriptor, runtimeCardDescriptor, CanonClient, ControlChannelPoller, buildCanonTurnContextV2, buildCanonGroupContext, buildParticipationHistorySnapshot, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildRuntimeInputOutcome, initRTDBAuth, isChunkedSendMessageError, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, renderCanonHostInboundContent, resolveCanonRuntimeConnection, sendMessageWithRetryChunked, splitTextByUtf8Bytes, verifyCanonRuntimeConnection, } from '@canonmsg/core';
|
|
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;
|
|
15
|
+
const SDK_PARTIAL_FINAL_NOTICE = 'This reply stops short because Canon could not deliver the remaining text.';
|
|
17
16
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
18
17
|
supportsInterrupt: false,
|
|
19
18
|
supportsInputInterrupt: false,
|
|
@@ -162,16 +161,6 @@ function safeRuntimeInputId(value, kind) {
|
|
|
162
161
|
? normalized
|
|
163
162
|
: `${kind}_${randomUUID()}`;
|
|
164
163
|
}
|
|
165
|
-
function safeRuntimeCardId(value) {
|
|
166
|
-
const raw = value?.trim() || `card_${randomUUID()}`;
|
|
167
|
-
const normalized = raw.replace(/[.#$\[\]\/\s]+/g, '_').slice(0, 80);
|
|
168
|
-
return RUNTIME_INPUT_ID_PATTERN.test(normalized)
|
|
169
|
-
? normalized
|
|
170
|
-
: `card_${randomUUID()}`;
|
|
171
|
-
}
|
|
172
|
-
function normalizeResponseUserId(value) {
|
|
173
|
-
return value?.trim() || undefined;
|
|
174
|
-
}
|
|
175
164
|
/**
|
|
176
165
|
* Part id for text this sdk split itself, mirroring the `-part-N` rule (and the
|
|
177
166
|
* 160-character id cap) that core's chunked sender applies to a split final, so
|
|
@@ -522,7 +511,7 @@ export class CanonAgent {
|
|
|
522
511
|
}
|
|
523
512
|
void this.publishAgentRuntime().catch(() => { });
|
|
524
513
|
}
|
|
525
|
-
describeCommands(
|
|
514
|
+
describeCommands() {
|
|
526
515
|
return this.buildRuntimeDescriptor().commands ?? [];
|
|
527
516
|
}
|
|
528
517
|
async publishRuntimeFacts(conversationId, facts) {
|
|
@@ -689,7 +678,7 @@ export class CanonAgent {
|
|
|
689
678
|
// 4. Start delivery
|
|
690
679
|
const { RealtimeManager } = await import('./realtime.js');
|
|
691
680
|
this.voiceEventsEnabled = Boolean(this.callStartedHandler || this.callEndedHandler);
|
|
692
|
-
const rtm = new RealtimeManager(this.options.apiKey, this.debouncer, agentId, this.options.streamUrl,
|
|
681
|
+
const rtm = new RealtimeManager(this.options.apiKey, this.debouncer, agentId, this.options.streamUrl, { enableVoiceEvents: this.voiceEventsEnabled });
|
|
693
682
|
if (this.voiceEventsEnabled) {
|
|
694
683
|
rtm.setCallHandlers({
|
|
695
684
|
onCallStarted: (payload) => {
|
|
@@ -1207,11 +1196,15 @@ export class CanonAgent {
|
|
|
1207
1196
|
createRuntimeStatePublisher() {
|
|
1208
1197
|
if (!this.agentId)
|
|
1209
1198
|
return null;
|
|
1199
|
+
// start() assigns rtdbHandle before agentId, so this can only be null
|
|
1200
|
+
// when the agent has not started — same condition as the guard above.
|
|
1201
|
+
if (!this.rtdbHandle)
|
|
1202
|
+
return null;
|
|
1210
1203
|
return createRuntimeStatePublisher({
|
|
1211
1204
|
agentId: this.agentId,
|
|
1212
1205
|
clientType: this.options.clientType ?? 'generic',
|
|
1213
1206
|
hostMode: this.options.runtimeControlSurface === 'host',
|
|
1214
|
-
|
|
1207
|
+
rtdb: this.rtdbHandle,
|
|
1215
1208
|
});
|
|
1216
1209
|
}
|
|
1217
1210
|
requireRuntimeStatePublisher() {
|
|
@@ -1360,7 +1353,7 @@ export class CanonAgent {
|
|
|
1360
1353
|
throwIfAborted();
|
|
1361
1354
|
const sendOptions = withActiveSelfContext(options);
|
|
1362
1355
|
const turnTrail = turnOutput.getFinalTrail();
|
|
1363
|
-
const
|
|
1356
|
+
const finalOptions = {
|
|
1364
1357
|
...sendOptions,
|
|
1365
1358
|
metadata: {
|
|
1366
1359
|
...(sendOptions.metadata ?? {}),
|
|
@@ -1368,7 +1361,31 @@ export class CanonAgent {
|
|
|
1368
1361
|
turnSemantics: 'turn_complete',
|
|
1369
1362
|
...(turnTrail.length > 0 ? { turnTrail } : {}),
|
|
1370
1363
|
},
|
|
1371
|
-
}
|
|
1364
|
+
};
|
|
1365
|
+
let result;
|
|
1366
|
+
try {
|
|
1367
|
+
result = await sendDurableMessage(text, finalOptions, ['sdk', 'final', conversationId, turnId]);
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
const chunked = isChunkedSendMessageError(error) ? error : null;
|
|
1371
|
+
if (!chunked || chunked.deliveredMessageIds.length === 0 || isAbortLikeError(error)) {
|
|
1372
|
+
throw error;
|
|
1373
|
+
}
|
|
1374
|
+
const requestedReplyBehavior = sendOptions.metadata?.replyBehavior;
|
|
1375
|
+
const notice = await sendDurableMessage(SDK_PARTIAL_FINAL_NOTICE, {
|
|
1376
|
+
...sendOptions,
|
|
1377
|
+
messageId: buildSdkMessageId(['sdk', 'final-incomplete', conversationId, turnId]),
|
|
1378
|
+
metadata: {
|
|
1379
|
+
turnId,
|
|
1380
|
+
turnSemantics: 'turn_complete',
|
|
1381
|
+
...(requestedReplyBehavior ? { replyBehavior: requestedReplyBehavior } : {}),
|
|
1382
|
+
},
|
|
1383
|
+
}, ['sdk', 'final-incomplete', conversationId, turnId]);
|
|
1384
|
+
result = {
|
|
1385
|
+
messageId: notice.messageId,
|
|
1386
|
+
messageIds: [...chunked.deliveredMessageIds, ...notice.messageIds],
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1372
1389
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
1373
1390
|
try {
|
|
1374
1391
|
await this.typingSignals.clear(conversationId);
|
|
@@ -1476,6 +1493,8 @@ export class CanonAgent {
|
|
|
1476
1493
|
messageId,
|
|
1477
1494
|
}, {
|
|
1478
1495
|
sleep: (ms) => sleepWithAbort(ms, abortController.signal),
|
|
1496
|
+
}, {
|
|
1497
|
+
resumable: true,
|
|
1479
1498
|
});
|
|
1480
1499
|
// `messageId` stays the single id callers expect. When the text was
|
|
1481
1500
|
// chunked it points at the LAST part — the message that carries
|
|
@@ -1539,7 +1558,7 @@ export class CanonAgent {
|
|
|
1539
1558
|
agent,
|
|
1540
1559
|
membershipChange,
|
|
1541
1560
|
});
|
|
1542
|
-
const participationHistory = buildParticipationHistorySnapshot(history
|
|
1561
|
+
const participationHistory = buildParticipationHistorySnapshot(history);
|
|
1543
1562
|
const turnContext = buildCanonTurnContextV2({
|
|
1544
1563
|
content: latestMessage ? renderCanonHostInboundContent(latestMessage) : '[Empty message]',
|
|
1545
1564
|
conversationId,
|
|
@@ -1667,7 +1686,6 @@ export class CanonAgent {
|
|
|
1667
1686
|
const inputId = safeRuntimeInputId(request.inputId, request.kind);
|
|
1668
1687
|
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1669
1688
|
const expiresAtMs = Date.now() + timeoutMs;
|
|
1670
|
-
const expiresAt = new Date(expiresAtMs).toISOString();
|
|
1671
1689
|
let result = { status: 'timeout', inputId };
|
|
1672
1690
|
let requestCreated = false;
|
|
1673
1691
|
const ownerOnly = request.kind === 'secret'
|
|
@@ -1772,23 +1790,10 @@ export class CanonAgent {
|
|
|
1772
1790
|
};
|
|
1773
1791
|
const sendCard = async (request) => {
|
|
1774
1792
|
throwIfAborted();
|
|
1775
|
-
const cardId =
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
? request.expiresAt
|
|
1780
|
-
: typeof request.expiresAt === 'string'
|
|
1781
|
-
? Date.parse(request.expiresAt)
|
|
1782
|
-
: null;
|
|
1783
|
-
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1784
|
-
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1785
|
-
? explicitExpiresAt
|
|
1786
|
-
: Date.now() + timeoutMs;
|
|
1787
|
-
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1788
|
-
?? triggeringHumanId;
|
|
1789
|
-
const routedRequest = responseUserId
|
|
1790
|
-
? { ...request, responseUserId }
|
|
1791
|
-
: request;
|
|
1793
|
+
const { cardId, expiresAtMs, routedRequest } = resolveRuntimeCardRouting({
|
|
1794
|
+
request,
|
|
1795
|
+
triggeringHumanId,
|
|
1796
|
+
});
|
|
1792
1797
|
// Fire-and-forget: post the durable card and return. The backend treats an
|
|
1793
1798
|
// action-less card as display (no pending state, no response expected).
|
|
1794
1799
|
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
@@ -1817,23 +1822,10 @@ export class CanonAgent {
|
|
|
1817
1822
|
&& request.card.blocks.some((block) => block.kind === 'actions');
|
|
1818
1823
|
if (!hasActions)
|
|
1819
1824
|
return sendCard(request);
|
|
1820
|
-
const cardId =
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
? request.expiresAt
|
|
1825
|
-
: typeof request.expiresAt === 'string'
|
|
1826
|
-
? Date.parse(request.expiresAt)
|
|
1827
|
-
: null;
|
|
1828
|
-
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1829
|
-
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1830
|
-
? explicitExpiresAt
|
|
1831
|
-
: Date.now() + timeoutMs;
|
|
1832
|
-
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1833
|
-
?? triggeringHumanId;
|
|
1834
|
-
const routedRequest = responseUserId
|
|
1835
|
-
? { ...request, responseUserId }
|
|
1836
|
-
: request;
|
|
1825
|
+
const { cardId, expiresAtMs, routedRequest } = resolveRuntimeCardRouting({
|
|
1826
|
+
request,
|
|
1827
|
+
triggeringHumanId,
|
|
1828
|
+
});
|
|
1837
1829
|
let result = { status: 'timeout', cardId };
|
|
1838
1830
|
let requestCreated = false;
|
|
1839
1831
|
let requestResolved = false;
|
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.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": "^
|
|
31
|
+
"@canonmsg/core": "^9.0.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
|
-
}
|