@canonmsg/agent-sdk 1.6.1 → 2.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 +27 -2
- package/dist/canon-agent.js +122 -51
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/turn-filter.d.ts +1 -1
- package/dist/turn-filter.js +2 -27
- package/dist/types.d.ts +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,10 +38,13 @@ No additional dependencies required — the SDK uses native `fetch` and `Readabl
|
|
|
38
38
|
| `deliveryMode` | `'auto' \| 'sse'` | `'auto'` | How the SDK receives new messages |
|
|
39
39
|
| `debounceMs` | `number` | `2000` | Batching window for incoming messages per conversation |
|
|
40
40
|
| `historyLimit` | `number` | `50` | Number of historical messages to fetch (max 100) |
|
|
41
|
+
| `autoMarkRead` | `boolean` | `true` | Advance Canon's read cursor explicitly after handling inbound messages. History fetches are read-only. |
|
|
41
42
|
| `sessions` | `SessionOptions` | `undefined` | Enable per-conversation session queues and persistent metadata |
|
|
42
43
|
| `clientType` | `AgentClientType` | `'generic'` | Agent runtime label used for Canon capability detection |
|
|
43
44
|
| `runtimeDescriptor` | `CanonRuntimeDescriptor` | minimal generic descriptor | Optional setup/live controls and runtime capability metadata for Canon UI |
|
|
44
45
|
| `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional interrupt / stop-clear handlers for Canon working-state controls |
|
|
46
|
+
| `runtimeControlSurface` | `'agent' \| 'host'` | `'agent'` | Runtime publishing surface. Use `host` when this SDK agent owns live runtime controls. |
|
|
47
|
+
| `runtimePrimitives` | `RuntimePrimitiveHandlers` | `undefined` | Optional typed primitive command handlers for descriptor-backed runtime commands |
|
|
45
48
|
| `sessionState` | `boolean` | `false` | Publish RTDB session-state for the conversations this agent is active in |
|
|
46
49
|
|
|
47
50
|
### Optional runtime controls
|
|
@@ -154,17 +157,38 @@ The `message` event handler receives a context object with:
|
|
|
154
157
|
|---|---|---|
|
|
155
158
|
| `messages` | `CanonMessage[]` | New messages in this batch (debounced, sorted by time) |
|
|
156
159
|
| `history` | `CanonMessage[]` | Last N messages before these new ones |
|
|
160
|
+
| `replyContext` | `CanonReplyContext \| null` | Resolved swipe-reply target for the latest inbound message, when available |
|
|
157
161
|
| `conversationId` | `string` | The conversation these messages belong to |
|
|
158
162
|
| `conversation` | `CanonConversation` | Full conversation metadata |
|
|
163
|
+
| `groupContext` | `CanonGroupContext \| undefined` | Lightweight group awareness for group conversations |
|
|
159
164
|
| `replyFinal` | `(text: string, options?) => Promise<{ messageId: string }>` | Send the durable final reply for a turn |
|
|
160
165
|
| `replyProgress` | `(text: string, options?) => Promise<{ turnId: string; durable: boolean; messageId: string \| null }>` | Update the live turn progress; add `durable: true` to also persist it |
|
|
166
|
+
| `deleteMessage` | `(messageId: string) => Promise<void>` | Soft-delete a message sent by this agent |
|
|
167
|
+
| `markAsRead` | `() => Promise<void>` | Advance this agent's read cursor for the conversation |
|
|
168
|
+
| `leave` | `() => Promise<void>` | Leave the current group conversation |
|
|
169
|
+
| `react` | `(messageId, emoji) => Promise<void>` | Toggle an emoji reaction |
|
|
170
|
+
| `addMember` / `removeMember` | functions | Manage group members when the agent has permission |
|
|
171
|
+
| `sendContextualMessage` | function | Send into another conversation with private self-context from this conversation |
|
|
172
|
+
| `reachOut` | function | Act on a Canon contact card using live admission resolution |
|
|
161
173
|
| `agent` | `AgentContext` | Trusted Canon agent identity and access context |
|
|
174
|
+
| `activeSelfContextId` | `string \| null` | Active private self-context id for this turn |
|
|
175
|
+
| `selfContexts` | `CanonSelfContext[] \| undefined` | Private context explaining this agent's cross-session actions |
|
|
176
|
+
| `provenance` | `CanonRuntimeProvenance` | Canon-computed sender/conversation context for the latest inbound message in this batch |
|
|
177
|
+
| `requestApproval` | `(request) => Promise<ApprovalResult>` | Render a Canon approval card, wait for a response, and return the decision to the runtime |
|
|
178
|
+
| `requestRuntimeInput` | `(request) => Promise<RuntimeInputResult>` | Render a Canon input card for clarification, sudo, or secret values |
|
|
162
179
|
| `media` | `{ materialize, uploadFile, replyWithFile }` | Canon-managed access to real media bytes via `~/.canon/media-cache` plus local-file uploads back into Canon |
|
|
163
180
|
| `session` | `SessionInfo \| undefined` | Per-conversation queue/session state when sessions are enabled |
|
|
164
181
|
| `turn` | `TurnController \| undefined` | Live turn-state helpers for thinking/streaming/tool/waiting-input |
|
|
182
|
+
| `abortSignal` | `AbortSignal` | Cooperative cancellation signal for interrupt/stop handling |
|
|
165
183
|
|
|
166
184
|
Messages from the agent itself are automatically filtered out -- your handler only receives messages from other participants.
|
|
167
185
|
|
|
186
|
+
`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. Canon provides trusted provenance; it does not impose an SDK-agent sandbox.
|
|
187
|
+
|
|
188
|
+
### Human-in-the-loop cards
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
168
192
|
## Contact Request Awareness
|
|
169
193
|
|
|
170
194
|
Agents can also observe contact-request lifecycle events without becoming the approver:
|
|
@@ -252,7 +276,7 @@ const { requestId, pollToken } = await CanonAgent.register({
|
|
|
252
276
|
});
|
|
253
277
|
|
|
254
278
|
console.log('Registration submitted:', requestId);
|
|
255
|
-
|
|
279
|
+
await saveRegistrationPickup({ requestId, pollToken });
|
|
256
280
|
|
|
257
281
|
// 2. Poll for approval
|
|
258
282
|
const status = await CanonAgent.checkStatus(requestId, { pollToken });
|
|
@@ -260,12 +284,13 @@ console.log('Status:', status.status); // 'pending' | 'approved' | 'rejected'
|
|
|
260
284
|
|
|
261
285
|
if (status.status === 'approved' && status.apiKey) {
|
|
262
286
|
console.log('Agent ID:', status.agentId);
|
|
263
|
-
|
|
287
|
+
await saveAgentCredentials({ agentId: status.agentId, apiKey: status.apiKey });
|
|
264
288
|
await CanonAgent.ackStatus(requestId, { pollToken });
|
|
265
289
|
}
|
|
266
290
|
```
|
|
267
291
|
|
|
268
292
|
The approved response only includes the API key until you acknowledge delivery. Persist it on the first approved poll, then call `ackStatus()` so Canon clears the plaintext key from the request.
|
|
293
|
+
Replace `saveRegistrationPickup` and `saveAgentCredentials` with your own encrypted/local secret-store writes; do not print these values in logs.
|
|
269
294
|
|
|
270
295
|
## Error Handling
|
|
271
296
|
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApprovalManager, CanonClient, buildCanonGroupContext, createRuntimeStatePublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeInputOutcome, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, } from '@canonmsg/core';
|
|
1
|
+
import { ApprovalManager, CanonClient, buildCanonGroupContext, createTurnOutputController, createRuntimeStatePublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeInputOutcome, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, } from '@canonmsg/core';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { AuthManager } from './auth.js';
|
|
4
4
|
import { Debouncer } from './debouncer.js';
|
|
@@ -319,7 +319,6 @@ export class CanonAgent {
|
|
|
319
319
|
: null;
|
|
320
320
|
const consumed = manager.handleMessage(conversationId, {
|
|
321
321
|
senderId: message.senderId,
|
|
322
|
-
...(typeof message.text === 'string' ? { text: message.text } : {}),
|
|
323
322
|
...(metadataRecord ? { metadata: metadataRecord } : {}),
|
|
324
323
|
});
|
|
325
324
|
return !consumed && metadataRecord?.type !== 'approval_reply';
|
|
@@ -673,35 +672,33 @@ export class CanonAgent {
|
|
|
673
672
|
const hasInterrupt = this.hasInterruptSupport();
|
|
674
673
|
const hasStopAndDrop = this.hasStopAndDropSupport();
|
|
675
674
|
const hasNewSession = this.hasNewSessionSupport();
|
|
676
|
-
const
|
|
677
|
-
if (
|
|
675
|
+
const commands = [...(source.commands ?? [])].filter((command) => {
|
|
676
|
+
if (command.dispatch.kind === 'signal') {
|
|
677
|
+
if (command.dispatch.signal === 'interrupt')
|
|
678
|
+
return hasInterrupt;
|
|
679
|
+
if (command.dispatch.signal === 'stop_and_drop')
|
|
680
|
+
return hasStopAndDrop;
|
|
681
|
+
if (command.dispatch.signal === 'new_session')
|
|
682
|
+
return hasNewSession;
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
if (command.dispatch.kind !== 'primitive')
|
|
678
686
|
return true;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (action.dispatch.signal === 'stop_and_drop')
|
|
682
|
-
return hasStopAndDrop;
|
|
683
|
-
if (action.dispatch.signal === 'new_session')
|
|
684
|
-
return hasNewSession;
|
|
685
|
-
return false;
|
|
687
|
+
return this.primitiveHandlers.has(command.dispatch.primitive)
|
|
688
|
+
|| Boolean(this.primitiveFallbackHandler);
|
|
686
689
|
});
|
|
687
|
-
const hasInterruptAction =
|
|
688
|
-
const hasStopAndDropAction =
|
|
689
|
-
const hasNewSessionAction =
|
|
690
|
+
const hasInterruptAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'interrupt');
|
|
691
|
+
const hasStopAndDropAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'stop_and_drop');
|
|
692
|
+
const hasNewSessionAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'new_session');
|
|
690
693
|
if (hasInterrupt && !hasInterruptAction) {
|
|
691
|
-
|
|
694
|
+
commands.push(RUNTIME_STOP_ACTION);
|
|
692
695
|
}
|
|
693
696
|
if (hasStopAndDrop && this.sessionManager && !hasStopAndDropAction) {
|
|
694
|
-
|
|
697
|
+
commands.push(RUNTIME_STOP_AND_DROP_ACTION);
|
|
695
698
|
}
|
|
696
699
|
if (hasNewSession && !hasNewSessionAction) {
|
|
697
|
-
|
|
700
|
+
commands.push(RUNTIME_NEW_SESSION_ACTION);
|
|
698
701
|
}
|
|
699
|
-
const commands = [...(source.commands ?? [])].filter((command) => {
|
|
700
|
-
if (command.dispatch.kind !== 'primitive')
|
|
701
|
-
return true;
|
|
702
|
-
return this.primitiveHandlers.has(command.dispatch.primitive)
|
|
703
|
-
|| Boolean(this.primitiveFallbackHandler);
|
|
704
|
-
});
|
|
705
702
|
const hasCommandForPrimitive = (primitive) => commands.some((command) => (command.primitive === primitive
|
|
706
703
|
|| (command.dispatch.kind === 'primitive' && command.dispatch.primitive === primitive)));
|
|
707
704
|
for (const primitive of this.primitiveHandlers.keys()) {
|
|
@@ -714,7 +711,6 @@ export class CanonAgent {
|
|
|
714
711
|
supportsInterrupt: hasInterrupt,
|
|
715
712
|
supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
|
|
716
713
|
commands,
|
|
717
|
-
actions,
|
|
718
714
|
};
|
|
719
715
|
}
|
|
720
716
|
buildRuntimeCapabilities() {
|
|
@@ -1112,21 +1108,24 @@ export class CanonAgent {
|
|
|
1112
1108
|
: {}),
|
|
1113
1109
|
})).catch(() => { });
|
|
1114
1110
|
};
|
|
1111
|
+
const turnOutput = createTurnOutputController({
|
|
1112
|
+
turnId,
|
|
1113
|
+
mode: 'snapshot',
|
|
1114
|
+
writeSnapshot: (snapshot) => this.apiClient.setStreaming({
|
|
1115
|
+
conversationId,
|
|
1116
|
+
text: snapshot.text,
|
|
1117
|
+
status: snapshot.status,
|
|
1118
|
+
messageId: snapshot.messageId,
|
|
1119
|
+
turnId: snapshot.turnId,
|
|
1120
|
+
blocks: snapshot.blocks,
|
|
1121
|
+
}),
|
|
1122
|
+
clearSnapshot: () => this.apiClient.clearStreaming(conversationId),
|
|
1123
|
+
});
|
|
1115
1124
|
const setLiveState = async (state, text, streamingStatus) => {
|
|
1116
1125
|
throwIfAborted();
|
|
1117
1126
|
await writeTurn(state);
|
|
1118
1127
|
if (streamingStatus) {
|
|
1119
|
-
|
|
1120
|
-
await this.apiClient.setStreaming({
|
|
1121
|
-
conversationId,
|
|
1122
|
-
text: text ?? '',
|
|
1123
|
-
status: streamingStatus,
|
|
1124
|
-
messageId: turnId,
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
catch {
|
|
1128
|
-
// Non-critical
|
|
1129
|
-
}
|
|
1128
|
+
await turnOutput.setStatus(streamingStatus, text ?? '');
|
|
1130
1129
|
}
|
|
1131
1130
|
};
|
|
1132
1131
|
// Show thinking indicator and keep it alive (5s client-side expiry)
|
|
@@ -1140,12 +1139,7 @@ export class CanonAgent {
|
|
|
1140
1139
|
const thinkingKeepalive = setInterval(() => {
|
|
1141
1140
|
this.apiClient.setTyping(conversationId, true, 'thinking').catch(() => { });
|
|
1142
1141
|
if (turnState === 'thinking') {
|
|
1143
|
-
|
|
1144
|
-
conversationId,
|
|
1145
|
-
text: 'Thinking...',
|
|
1146
|
-
status: 'thinking',
|
|
1147
|
-
messageId: turnId,
|
|
1148
|
-
}).catch(() => { });
|
|
1142
|
+
turnOutput.setStatus('thinking', 'Thinking...').catch(() => { });
|
|
1149
1143
|
}
|
|
1150
1144
|
}, 3500);
|
|
1151
1145
|
try {
|
|
@@ -1181,13 +1175,14 @@ export class CanonAgent {
|
|
|
1181
1175
|
catch { }
|
|
1182
1176
|
throwIfAborted();
|
|
1183
1177
|
const sendOptions = withActiveSelfContext(options);
|
|
1178
|
+
const turnTrail = turnOutput.getFinalTrail();
|
|
1184
1179
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
1185
1180
|
...sendOptions,
|
|
1186
1181
|
metadata: {
|
|
1187
1182
|
...(sendOptions.metadata ?? {}),
|
|
1188
1183
|
turnId,
|
|
1189
1184
|
turnSemantics: 'turn_complete',
|
|
1190
|
-
|
|
1185
|
+
...(turnTrail.length > 0 ? { turnTrail } : {}),
|
|
1191
1186
|
},
|
|
1192
1187
|
});
|
|
1193
1188
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
@@ -1212,7 +1207,6 @@ export class CanonAgent {
|
|
|
1212
1207
|
...(sendOptionsWithContext.metadata ?? {}),
|
|
1213
1208
|
turnId,
|
|
1214
1209
|
turnSemantics: 'progress',
|
|
1215
|
-
turnComplete: false,
|
|
1216
1210
|
},
|
|
1217
1211
|
});
|
|
1218
1212
|
return { turnId, durable: true, messageId: result.messageId };
|
|
@@ -1314,7 +1308,6 @@ export class CanonAgent {
|
|
|
1314
1308
|
...(options.messageOptions?.metadata ?? {}),
|
|
1315
1309
|
turnId,
|
|
1316
1310
|
turnSemantics: 'turn_complete',
|
|
1317
|
-
turnComplete: true,
|
|
1318
1311
|
},
|
|
1319
1312
|
},
|
|
1320
1313
|
});
|
|
@@ -1331,7 +1324,14 @@ export class CanonAgent {
|
|
|
1331
1324
|
shouldPersistTurnState = true;
|
|
1332
1325
|
try {
|
|
1333
1326
|
try {
|
|
1334
|
-
await
|
|
1327
|
+
await turnOutput.addBlock({
|
|
1328
|
+
id: `approval:${request.runtimeId ?? request.toolName}:${turnId}`,
|
|
1329
|
+
kind: 'approval',
|
|
1330
|
+
status: 'pending',
|
|
1331
|
+
title: request.toolSummary ?? request.toolName,
|
|
1332
|
+
summary: request.risk ?? request.category,
|
|
1333
|
+
});
|
|
1334
|
+
await turnOutput.waitingInput();
|
|
1335
1335
|
}
|
|
1336
1336
|
catch { }
|
|
1337
1337
|
await writeTurn('waiting_input');
|
|
@@ -1353,6 +1353,12 @@ export class CanonAgent {
|
|
|
1353
1353
|
});
|
|
1354
1354
|
throwIfAborted();
|
|
1355
1355
|
shouldPersistTurnState = false;
|
|
1356
|
+
try {
|
|
1357
|
+
await turnOutput.completeBlock(`approval:${request.runtimeId ?? request.toolName}:${turnId}`, {
|
|
1358
|
+
summary: `Decision: ${result.decision}`,
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
catch { }
|
|
1356
1362
|
try {
|
|
1357
1363
|
await this.apiClient.setTyping(conversationId, true, 'thinking');
|
|
1358
1364
|
}
|
|
@@ -1401,6 +1407,7 @@ export class CanonAgent {
|
|
|
1401
1407
|
title: request.title,
|
|
1402
1408
|
prompt: request.prompt,
|
|
1403
1409
|
...(request.choices ? { choices: request.choices } : {}),
|
|
1410
|
+
...(request.questions ? { questions: request.questions } : {}),
|
|
1404
1411
|
...(request.secretName ? { secretName: request.secretName } : {}),
|
|
1405
1412
|
...(request.native ? { native: request.native } : {}),
|
|
1406
1413
|
...(request.sensitive !== undefined ? { sensitive: request.sensitive } : {}),
|
|
@@ -1408,7 +1415,14 @@ export class CanonAgent {
|
|
|
1408
1415
|
});
|
|
1409
1416
|
requestCreated = true;
|
|
1410
1417
|
try {
|
|
1411
|
-
await
|
|
1418
|
+
await turnOutput.addBlock({
|
|
1419
|
+
id: `input:${inputId}`,
|
|
1420
|
+
kind: 'input',
|
|
1421
|
+
status: 'pending',
|
|
1422
|
+
title: request.title ?? request.prompt ?? request.kind,
|
|
1423
|
+
summary: request.kind,
|
|
1424
|
+
});
|
|
1425
|
+
await turnOutput.waitingInput();
|
|
1412
1426
|
}
|
|
1413
1427
|
catch { }
|
|
1414
1428
|
await writeTurn('waiting_input');
|
|
@@ -1428,6 +1442,7 @@ export class CanonAgent {
|
|
|
1428
1442
|
result = {
|
|
1429
1443
|
status: 'submitted',
|
|
1430
1444
|
value: response.value,
|
|
1445
|
+
answers: response.answers,
|
|
1431
1446
|
inputId,
|
|
1432
1447
|
};
|
|
1433
1448
|
break;
|
|
@@ -1451,7 +1466,12 @@ export class CanonAgent {
|
|
|
1451
1466
|
inputId,
|
|
1452
1467
|
});
|
|
1453
1468
|
if (response.status === 'submitted') {
|
|
1454
|
-
result = {
|
|
1469
|
+
result = {
|
|
1470
|
+
status: 'submitted',
|
|
1471
|
+
value: response.value,
|
|
1472
|
+
answers: response.answers,
|
|
1473
|
+
inputId,
|
|
1474
|
+
};
|
|
1455
1475
|
}
|
|
1456
1476
|
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1457
1477
|
result = { status: response.status, inputId };
|
|
@@ -1474,6 +1494,12 @@ export class CanonAgent {
|
|
|
1474
1494
|
});
|
|
1475
1495
|
throwIfAborted();
|
|
1476
1496
|
shouldPersistTurnState = false;
|
|
1497
|
+
try {
|
|
1498
|
+
await turnOutput.completeBlock(`input:${inputId}`, {
|
|
1499
|
+
summary: `Input ${result.status}`,
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
catch { }
|
|
1477
1503
|
try {
|
|
1478
1504
|
await this.apiClient.setTyping(conversationId, true, 'thinking');
|
|
1479
1505
|
}
|
|
@@ -1499,6 +1525,7 @@ export class CanonAgent {
|
|
|
1499
1525
|
catch { }
|
|
1500
1526
|
throwIfAborted();
|
|
1501
1527
|
try {
|
|
1528
|
+
const turnTrail = turnOutput.getFinalTrail();
|
|
1502
1529
|
const result = await sendMediaFileMessage(this.apiClient, conversationId, filePath, text, {
|
|
1503
1530
|
...(options?.replyTo ? { replyTo: options.replyTo } : {}),
|
|
1504
1531
|
...(options?.replyToPosition != null
|
|
@@ -1512,7 +1539,7 @@ export class CanonAgent {
|
|
|
1512
1539
|
...(options?.metadata ?? {}),
|
|
1513
1540
|
turnId,
|
|
1514
1541
|
turnSemantics: 'turn_complete',
|
|
1515
|
-
|
|
1542
|
+
...(turnTrail.length > 0 ? { turnTrail } : {}),
|
|
1516
1543
|
},
|
|
1517
1544
|
...(options?.fileName ? { fileName: options.fileName } : {}),
|
|
1518
1545
|
...(options?.mimeType ? { mimeType: options.mimeType } : {}),
|
|
@@ -1586,13 +1613,57 @@ export class CanonAgent {
|
|
|
1586
1613
|
setStreaming: async (text) => {
|
|
1587
1614
|
await setLiveState('streaming', text, 'streaming');
|
|
1588
1615
|
},
|
|
1616
|
+
appendDelta: (delta) => {
|
|
1617
|
+
throwIfAborted();
|
|
1618
|
+
turnState = 'streaming';
|
|
1619
|
+
turnOutput.appendDelta(delta);
|
|
1620
|
+
void writeTurn('streaming');
|
|
1621
|
+
},
|
|
1622
|
+
appendBlock: (block) => {
|
|
1623
|
+
throwIfAborted();
|
|
1624
|
+
turnState = 'streaming';
|
|
1625
|
+
turnOutput.appendBlock(block);
|
|
1626
|
+
void writeTurn('streaming');
|
|
1627
|
+
},
|
|
1628
|
+
addBlock: async (block) => {
|
|
1629
|
+
throwIfAborted();
|
|
1630
|
+
return turnOutput.addBlock(block);
|
|
1631
|
+
},
|
|
1632
|
+
updateBlock: async (id, patch) => {
|
|
1633
|
+
throwIfAborted();
|
|
1634
|
+
return turnOutput.updateBlock(id, patch);
|
|
1635
|
+
},
|
|
1636
|
+
completeBlock: async (id, patch) => {
|
|
1637
|
+
throwIfAborted();
|
|
1638
|
+
return turnOutput.completeBlock(id, patch);
|
|
1639
|
+
},
|
|
1640
|
+
failBlock: async (id, patch) => {
|
|
1641
|
+
throwIfAborted();
|
|
1642
|
+
return turnOutput.failBlock(id, patch);
|
|
1643
|
+
},
|
|
1644
|
+
replaceSnapshot: async (text) => {
|
|
1645
|
+
await setLiveState('streaming', text, 'streaming');
|
|
1646
|
+
},
|
|
1647
|
+
flush: async () => {
|
|
1648
|
+
await turnOutput.flush();
|
|
1649
|
+
},
|
|
1650
|
+
clear: async () => {
|
|
1651
|
+
await turnOutput.clear();
|
|
1652
|
+
},
|
|
1589
1653
|
setTool: async (text) => {
|
|
1590
|
-
await
|
|
1654
|
+
await writeTurn('tool');
|
|
1655
|
+
await turnOutput.addBlock({
|
|
1656
|
+
id: `tool:${Date.now()}`,
|
|
1657
|
+
kind: 'tool',
|
|
1658
|
+
status: 'running',
|
|
1659
|
+
title: text,
|
|
1660
|
+
});
|
|
1661
|
+
await turnOutput.setStatus('tool');
|
|
1591
1662
|
},
|
|
1592
1663
|
setWaitingInput: async (text) => {
|
|
1593
1664
|
shouldPersistTurnState = true;
|
|
1594
1665
|
try {
|
|
1595
|
-
await
|
|
1666
|
+
await turnOutput.waitingInput();
|
|
1596
1667
|
}
|
|
1597
1668
|
catch { }
|
|
1598
1669
|
await writeTurn('waiting_input');
|
|
@@ -1648,7 +1719,7 @@ export class CanonAgent {
|
|
|
1648
1719
|
}
|
|
1649
1720
|
catch { }
|
|
1650
1721
|
try {
|
|
1651
|
-
await
|
|
1722
|
+
await turnOutput.clear();
|
|
1652
1723
|
}
|
|
1653
1724
|
catch { }
|
|
1654
1725
|
if (runtimeState && !shouldPersistTurnState) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { CanonAgent } from './canon-agent.js';
|
|
2
2
|
export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
|
|
3
|
-
export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule,
|
|
4
|
-
export type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalOutcomeMetadata, ApprovalReplyMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalResult, ApprovalRisk, CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SessionRule, } from '@canonmsg/core';
|
|
3
|
+
export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule, redactSecrets, } from '@canonmsg/core';
|
|
4
|
+
export type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalOutcomeMetadata, ApprovalReplyMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalResult, ApprovalRisk, CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, RuntimeInputAnswers, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SessionRule, } from '@canonmsg/core';
|
|
5
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';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CanonAgent } from './canon-agent.js';
|
|
2
|
-
export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule,
|
|
2
|
+
export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule, redactSecrets, } from '@canonmsg/core';
|
|
3
3
|
export { SessionManager } from './session-manager.js';
|
|
4
4
|
export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
|
package/dist/turn-filter.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ResolvedAgentBehaviorPolicy, type CanonMessage } from '@canonmsg/core';
|
|
2
|
-
export declare function shouldDispatchInboundMessage(
|
|
2
|
+
export declare function shouldDispatchInboundMessage(_conversationId: string, agentId: string, message: CanonMessage, options?: {
|
|
3
3
|
conversationType?: 'direct' | 'group' | 'unknown';
|
|
4
4
|
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
5
5
|
recentHumanCount?: number;
|
package/dist/turn-filter.js
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
-
import { evaluateParticipationPolicy,
|
|
2
|
-
function
|
|
3
|
-
const turnState = normalizeTurnState(value);
|
|
4
|
-
if (turnState) {
|
|
5
|
-
return {
|
|
6
|
-
state: turnState.state,
|
|
7
|
-
...(turnState.openedAt !== undefined ? { openedAt: turnState.openedAt } : {}),
|
|
8
|
-
...(turnState.updatedAt !== undefined ? { updatedAt: turnState.updatedAt } : {}),
|
|
9
|
-
...(turnState.turnUpdatedAt !== undefined ? { turnUpdatedAt: turnState.turnUpdatedAt } : {}),
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
export async function shouldDispatchInboundMessage(conversationId, agentId, message, options) {
|
|
1
|
+
import { evaluateParticipationPolicy, shouldTriggerAgentTurn, } from '@canonmsg/core';
|
|
2
|
+
export async function shouldDispatchInboundMessage(_conversationId, agentId, message, options) {
|
|
15
3
|
if (message.senderId === agentId)
|
|
16
4
|
return false;
|
|
17
|
-
let senderTurnState = null;
|
|
18
|
-
try {
|
|
19
|
-
const [turnState, sessionState] = await Promise.all([
|
|
20
|
-
rtdbRead(`/turn-state/${conversationId}/${message.senderId}`),
|
|
21
|
-
rtdbRead(`/session-state/${conversationId}/${message.senderId}`),
|
|
22
|
-
]);
|
|
23
|
-
senderTurnState = normalizeRuntimeTurnState(turnState)
|
|
24
|
-
?? normalizeRuntimeTurnState(sessionState);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
senderTurnState = null;
|
|
28
|
-
}
|
|
29
5
|
const triggerDecision = shouldTriggerAgentTurn({
|
|
30
6
|
senderType: message.senderType,
|
|
31
7
|
metadata: message.metadata,
|
|
32
|
-
senderTurnState,
|
|
33
8
|
});
|
|
34
9
|
if (!triggerDecision.allow)
|
|
35
10
|
return false;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SessionRule, ApprovalResult, } from '@canonmsg/core';
|
|
2
|
-
import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
|
|
1
|
+
export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, TurnOutputBlock, TurnOutputBlockInput, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SessionRule, ApprovalResult, } from '@canonmsg/core';
|
|
2
|
+
import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SendMessageOptions, SendContextualSelfContextInput, SessionConfig, TurnOutputBlock, TurnOutputBlockInput } from '@canonmsg/core';
|
|
3
3
|
import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
|
|
4
4
|
export interface ProgressMessageOptions extends SendMessageOptions {
|
|
5
5
|
/**
|
|
@@ -31,6 +31,15 @@ export interface TurnController {
|
|
|
31
31
|
state: import('@canonmsg/core').TurnLifecycleState;
|
|
32
32
|
setThinking: (text?: string) => Promise<void>;
|
|
33
33
|
setStreaming: (text: string) => Promise<void>;
|
|
34
|
+
appendDelta: (delta: string) => void;
|
|
35
|
+
appendBlock: (block: string) => void;
|
|
36
|
+
addBlock: (block: TurnOutputBlockInput) => Promise<TurnOutputBlock>;
|
|
37
|
+
updateBlock: (id: string, patch: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence'>>) => Promise<TurnOutputBlock | null>;
|
|
38
|
+
completeBlock: (id: string, patch?: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence' | 'status'>>) => Promise<TurnOutputBlock | null>;
|
|
39
|
+
failBlock: (id: string, patch?: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence' | 'status'>>) => Promise<TurnOutputBlock | null>;
|
|
40
|
+
replaceSnapshot: (text: string) => Promise<void>;
|
|
41
|
+
flush: () => Promise<void>;
|
|
42
|
+
clear: () => Promise<void>;
|
|
34
43
|
setTool: (text: string) => Promise<void>;
|
|
35
44
|
setWaitingInput: (text?: string) => Promise<void>;
|
|
36
45
|
}
|
|
@@ -61,6 +70,7 @@ export interface RuntimeInputRequest {
|
|
|
61
70
|
title: string;
|
|
62
71
|
prompt: string;
|
|
63
72
|
choices?: RuntimeInputChoice[];
|
|
73
|
+
questions?: RuntimeInputQuestion[];
|
|
64
74
|
secretName?: string;
|
|
65
75
|
sensitive?: boolean;
|
|
66
76
|
runtimeId?: string;
|
|
@@ -72,6 +82,7 @@ export interface RuntimeInputRequest {
|
|
|
72
82
|
export interface RuntimeInputResult {
|
|
73
83
|
status: 'submitted' | 'cancelled' | 'timeout';
|
|
74
84
|
value?: string;
|
|
85
|
+
answers?: RuntimeInputAnswers;
|
|
75
86
|
inputId: string;
|
|
76
87
|
}
|
|
77
88
|
export interface MessageHandlerContext {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.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": "^1.0.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|