@canonmsg/agent-sdk 1.7.0 → 2.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/README.md +27 -2
- package/dist/canon-agent.js +291 -30
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/runtime-card.d.ts +29 -0
- package/dist/runtime-card.js +22 -0
- package/dist/turn-filter.d.ts +1 -1
- package/dist/turn-filter.js +2 -27
- package/dist/types.d.ts +37 -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,7 +1,8 @@
|
|
|
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';
|
|
1
|
+
import { ApprovalManager, CanonClient, buildCanonGroupContext, createTurnOutputController, createRuntimeStatePublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, 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';
|
|
5
|
+
import { buildRuntimeCardCreateArgs } from './runtime-card.js';
|
|
5
6
|
import { materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, uploadMediaFile, } from './media.js';
|
|
6
7
|
import { SessionManager } from './session-manager.js';
|
|
7
8
|
const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
|
|
@@ -23,6 +24,17 @@ const DEFAULT_SDK_RUNTIME_DESCRIPTOR = {
|
|
|
23
24
|
runtimeControls: [],
|
|
24
25
|
supportsInterrupt: false,
|
|
25
26
|
streamingTextMode: 'snapshot',
|
|
27
|
+
runtimeCards: {
|
|
28
|
+
rich: {
|
|
29
|
+
schema: 'canon.card.v1',
|
|
30
|
+
lifecycle: 'blocking_requires_action',
|
|
31
|
+
responder: 'agent_owner',
|
|
32
|
+
result: 'action_or_values',
|
|
33
|
+
maxTimeoutMs: 30 * 60_000,
|
|
34
|
+
blockKinds: ['summary', 'metricGrid', 'chart', 'table', 'list', 'callout', 'actions'],
|
|
35
|
+
native: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
26
38
|
};
|
|
27
39
|
const STANDARD_PRIMITIVE_COMMANDS = {
|
|
28
40
|
'runtime.status': {
|
|
@@ -146,6 +158,13 @@ function safeRuntimeInputId(value, kind) {
|
|
|
146
158
|
? normalized
|
|
147
159
|
: `${kind}_${randomUUID()}`;
|
|
148
160
|
}
|
|
161
|
+
function safeRuntimeCardId(value) {
|
|
162
|
+
const raw = value?.trim() || `card_${randomUUID()}`;
|
|
163
|
+
const normalized = raw.replace(/[.#$\[\]\/\s]+/g, '_').slice(0, 80);
|
|
164
|
+
return RUNTIME_INPUT_ID_PATTERN.test(normalized)
|
|
165
|
+
? normalized
|
|
166
|
+
: `card_${randomUUID()}`;
|
|
167
|
+
}
|
|
149
168
|
function isRuntimePrimitiveId(value) {
|
|
150
169
|
return value === 'runtime.status'
|
|
151
170
|
|| value === 'runtime.reasoning.set'
|
|
@@ -319,7 +338,6 @@ export class CanonAgent {
|
|
|
319
338
|
: null;
|
|
320
339
|
const consumed = manager.handleMessage(conversationId, {
|
|
321
340
|
senderId: message.senderId,
|
|
322
|
-
...(typeof message.text === 'string' ? { text: message.text } : {}),
|
|
323
341
|
...(metadataRecord ? { metadata: metadataRecord } : {}),
|
|
324
342
|
});
|
|
325
343
|
return !consumed && metadataRecord?.type !== 'approval_reply';
|
|
@@ -673,35 +691,33 @@ export class CanonAgent {
|
|
|
673
691
|
const hasInterrupt = this.hasInterruptSupport();
|
|
674
692
|
const hasStopAndDrop = this.hasStopAndDropSupport();
|
|
675
693
|
const hasNewSession = this.hasNewSessionSupport();
|
|
676
|
-
const
|
|
677
|
-
if (
|
|
694
|
+
const commands = [...(source.commands ?? [])].filter((command) => {
|
|
695
|
+
if (command.dispatch.kind === 'signal') {
|
|
696
|
+
if (command.dispatch.signal === 'interrupt')
|
|
697
|
+
return hasInterrupt;
|
|
698
|
+
if (command.dispatch.signal === 'stop_and_drop')
|
|
699
|
+
return hasStopAndDrop;
|
|
700
|
+
if (command.dispatch.signal === 'new_session')
|
|
701
|
+
return hasNewSession;
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
if (command.dispatch.kind !== 'primitive')
|
|
678
705
|
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;
|
|
706
|
+
return this.primitiveHandlers.has(command.dispatch.primitive)
|
|
707
|
+
|| Boolean(this.primitiveFallbackHandler);
|
|
686
708
|
});
|
|
687
|
-
const hasInterruptAction =
|
|
688
|
-
const hasStopAndDropAction =
|
|
689
|
-
const hasNewSessionAction =
|
|
709
|
+
const hasInterruptAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'interrupt');
|
|
710
|
+
const hasStopAndDropAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'stop_and_drop');
|
|
711
|
+
const hasNewSessionAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'new_session');
|
|
690
712
|
if (hasInterrupt && !hasInterruptAction) {
|
|
691
|
-
|
|
713
|
+
commands.push(RUNTIME_STOP_ACTION);
|
|
692
714
|
}
|
|
693
715
|
if (hasStopAndDrop && this.sessionManager && !hasStopAndDropAction) {
|
|
694
|
-
|
|
716
|
+
commands.push(RUNTIME_STOP_AND_DROP_ACTION);
|
|
695
717
|
}
|
|
696
718
|
if (hasNewSession && !hasNewSessionAction) {
|
|
697
|
-
|
|
719
|
+
commands.push(RUNTIME_NEW_SESSION_ACTION);
|
|
698
720
|
}
|
|
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
721
|
const hasCommandForPrimitive = (primitive) => commands.some((command) => (command.primitive === primitive
|
|
706
722
|
|| (command.dispatch.kind === 'primitive' && command.dispatch.primitive === primitive)));
|
|
707
723
|
for (const primitive of this.primitiveHandlers.keys()) {
|
|
@@ -714,7 +730,6 @@ export class CanonAgent {
|
|
|
714
730
|
supportsInterrupt: hasInterrupt,
|
|
715
731
|
supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
|
|
716
732
|
commands,
|
|
717
|
-
actions,
|
|
718
733
|
};
|
|
719
734
|
}
|
|
720
735
|
buildRuntimeCapabilities() {
|
|
@@ -1121,6 +1136,7 @@ export class CanonAgent {
|
|
|
1121
1136
|
status: snapshot.status,
|
|
1122
1137
|
messageId: snapshot.messageId,
|
|
1123
1138
|
turnId: snapshot.turnId,
|
|
1139
|
+
blocks: snapshot.blocks,
|
|
1124
1140
|
}),
|
|
1125
1141
|
clearSnapshot: () => this.apiClient.clearStreaming(conversationId),
|
|
1126
1142
|
});
|
|
@@ -1178,13 +1194,14 @@ export class CanonAgent {
|
|
|
1178
1194
|
catch { }
|
|
1179
1195
|
throwIfAborted();
|
|
1180
1196
|
const sendOptions = withActiveSelfContext(options);
|
|
1197
|
+
const turnTrail = turnOutput.getFinalTrail();
|
|
1181
1198
|
const result = await this.apiClient.sendMessage(conversationId, text, {
|
|
1182
1199
|
...sendOptions,
|
|
1183
1200
|
metadata: {
|
|
1184
1201
|
...(sendOptions.metadata ?? {}),
|
|
1185
1202
|
turnId,
|
|
1186
1203
|
turnSemantics: 'turn_complete',
|
|
1187
|
-
|
|
1204
|
+
...(turnTrail.length > 0 ? { turnTrail } : {}),
|
|
1188
1205
|
},
|
|
1189
1206
|
});
|
|
1190
1207
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
@@ -1209,7 +1226,6 @@ export class CanonAgent {
|
|
|
1209
1226
|
...(sendOptionsWithContext.metadata ?? {}),
|
|
1210
1227
|
turnId,
|
|
1211
1228
|
turnSemantics: 'progress',
|
|
1212
|
-
turnComplete: false,
|
|
1213
1229
|
},
|
|
1214
1230
|
});
|
|
1215
1231
|
return { turnId, durable: true, messageId: result.messageId };
|
|
@@ -1311,7 +1327,6 @@ export class CanonAgent {
|
|
|
1311
1327
|
...(options.messageOptions?.metadata ?? {}),
|
|
1312
1328
|
turnId,
|
|
1313
1329
|
turnSemantics: 'turn_complete',
|
|
1314
|
-
turnComplete: true,
|
|
1315
1330
|
},
|
|
1316
1331
|
},
|
|
1317
1332
|
});
|
|
@@ -1328,6 +1343,13 @@ export class CanonAgent {
|
|
|
1328
1343
|
shouldPersistTurnState = true;
|
|
1329
1344
|
try {
|
|
1330
1345
|
try {
|
|
1346
|
+
await turnOutput.addBlock({
|
|
1347
|
+
id: `approval:${request.runtimeId ?? request.toolName}:${turnId}`,
|
|
1348
|
+
kind: 'approval',
|
|
1349
|
+
status: 'pending',
|
|
1350
|
+
title: request.toolSummary ?? request.toolName,
|
|
1351
|
+
summary: request.risk ?? request.category,
|
|
1352
|
+
});
|
|
1331
1353
|
await turnOutput.waitingInput();
|
|
1332
1354
|
}
|
|
1333
1355
|
catch { }
|
|
@@ -1350,6 +1372,12 @@ export class CanonAgent {
|
|
|
1350
1372
|
});
|
|
1351
1373
|
throwIfAborted();
|
|
1352
1374
|
shouldPersistTurnState = false;
|
|
1375
|
+
try {
|
|
1376
|
+
await turnOutput.completeBlock(`approval:${request.runtimeId ?? request.toolName}:${turnId}`, {
|
|
1377
|
+
summary: `Decision: ${result.decision}`,
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
catch { }
|
|
1353
1381
|
try {
|
|
1354
1382
|
await this.apiClient.setTyping(conversationId, true, 'thinking');
|
|
1355
1383
|
}
|
|
@@ -1398,6 +1426,7 @@ export class CanonAgent {
|
|
|
1398
1426
|
title: request.title,
|
|
1399
1427
|
prompt: request.prompt,
|
|
1400
1428
|
...(request.choices ? { choices: request.choices } : {}),
|
|
1429
|
+
...(request.questions ? { questions: request.questions } : {}),
|
|
1401
1430
|
...(request.secretName ? { secretName: request.secretName } : {}),
|
|
1402
1431
|
...(request.native ? { native: request.native } : {}),
|
|
1403
1432
|
...(request.sensitive !== undefined ? { sensitive: request.sensitive } : {}),
|
|
@@ -1405,6 +1434,13 @@ export class CanonAgent {
|
|
|
1405
1434
|
});
|
|
1406
1435
|
requestCreated = true;
|
|
1407
1436
|
try {
|
|
1437
|
+
await turnOutput.addBlock({
|
|
1438
|
+
id: `input:${inputId}`,
|
|
1439
|
+
kind: 'input',
|
|
1440
|
+
status: 'pending',
|
|
1441
|
+
title: request.title ?? request.prompt ?? request.kind,
|
|
1442
|
+
summary: request.kind,
|
|
1443
|
+
});
|
|
1408
1444
|
await turnOutput.waitingInput();
|
|
1409
1445
|
}
|
|
1410
1446
|
catch { }
|
|
@@ -1425,6 +1461,7 @@ export class CanonAgent {
|
|
|
1425
1461
|
result = {
|
|
1426
1462
|
status: 'submitted',
|
|
1427
1463
|
value: response.value,
|
|
1464
|
+
answers: response.answers,
|
|
1428
1465
|
inputId,
|
|
1429
1466
|
};
|
|
1430
1467
|
break;
|
|
@@ -1448,7 +1485,12 @@ export class CanonAgent {
|
|
|
1448
1485
|
inputId,
|
|
1449
1486
|
});
|
|
1450
1487
|
if (response.status === 'submitted') {
|
|
1451
|
-
result = {
|
|
1488
|
+
result = {
|
|
1489
|
+
status: 'submitted',
|
|
1490
|
+
value: response.value,
|
|
1491
|
+
answers: response.answers,
|
|
1492
|
+
inputId,
|
|
1493
|
+
};
|
|
1452
1494
|
}
|
|
1453
1495
|
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1454
1496
|
result = { status: response.status, inputId };
|
|
@@ -1471,6 +1513,12 @@ export class CanonAgent {
|
|
|
1471
1513
|
});
|
|
1472
1514
|
throwIfAborted();
|
|
1473
1515
|
shouldPersistTurnState = false;
|
|
1516
|
+
try {
|
|
1517
|
+
await turnOutput.completeBlock(`input:${inputId}`, {
|
|
1518
|
+
summary: `Input ${result.status}`,
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
catch { }
|
|
1474
1522
|
try {
|
|
1475
1523
|
await this.apiClient.setTyping(conversationId, true, 'thinking');
|
|
1476
1524
|
}
|
|
@@ -1483,6 +1531,193 @@ export class CanonAgent {
|
|
|
1483
1531
|
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1484
1532
|
throw error;
|
|
1485
1533
|
}
|
|
1534
|
+
if (!requestCreated) {
|
|
1535
|
+
throw error;
|
|
1536
|
+
}
|
|
1537
|
+
shouldPersistTurnState = false;
|
|
1538
|
+
return result;
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
const sendCard = async (request) => {
|
|
1542
|
+
throwIfAborted();
|
|
1543
|
+
const cardId = safeRuntimeCardId(request.cardId ?? request.card.cardId);
|
|
1544
|
+
const explicitExpiresAt = request.expiresAt instanceof Date
|
|
1545
|
+
? request.expiresAt.getTime()
|
|
1546
|
+
: typeof request.expiresAt === 'number'
|
|
1547
|
+
? request.expiresAt
|
|
1548
|
+
: typeof request.expiresAt === 'string'
|
|
1549
|
+
? Date.parse(request.expiresAt)
|
|
1550
|
+
: null;
|
|
1551
|
+
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1552
|
+
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1553
|
+
? explicitExpiresAt
|
|
1554
|
+
: Date.now() + timeoutMs;
|
|
1555
|
+
// Fire-and-forget: post the durable card and return. The backend treats an
|
|
1556
|
+
// action-less card as display (no pending state, no response expected).
|
|
1557
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
|
|
1558
|
+
try {
|
|
1559
|
+
await turnOutput.addBlock({
|
|
1560
|
+
id: `card:${cardId}`,
|
|
1561
|
+
kind: 'status',
|
|
1562
|
+
status: 'completed',
|
|
1563
|
+
title: request.card.title,
|
|
1564
|
+
summary: request.card.template ?? 'runtime card',
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
catch { }
|
|
1568
|
+
return { status: 'displayed', cardId };
|
|
1569
|
+
};
|
|
1570
|
+
const requestCard = async (request) => {
|
|
1571
|
+
throwIfAborted();
|
|
1572
|
+
// Action-less cards are fire-and-forget display/report cards — never block.
|
|
1573
|
+
const hasActions = Array.isArray(request.card.blocks)
|
|
1574
|
+
&& request.card.blocks.some((block) => block.kind === 'actions');
|
|
1575
|
+
if (!hasActions)
|
|
1576
|
+
return sendCard(request);
|
|
1577
|
+
const cardId = safeRuntimeCardId(request.cardId ?? request.card.cardId);
|
|
1578
|
+
const explicitExpiresAt = request.expiresAt instanceof Date
|
|
1579
|
+
? request.expiresAt.getTime()
|
|
1580
|
+
: typeof request.expiresAt === 'number'
|
|
1581
|
+
? request.expiresAt
|
|
1582
|
+
: typeof request.expiresAt === 'string'
|
|
1583
|
+
? Date.parse(request.expiresAt)
|
|
1584
|
+
: null;
|
|
1585
|
+
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1586
|
+
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1587
|
+
? explicitExpiresAt
|
|
1588
|
+
: Date.now() + timeoutMs;
|
|
1589
|
+
let result = { status: 'timeout', cardId };
|
|
1590
|
+
let requestCreated = false;
|
|
1591
|
+
let requestResolved = false;
|
|
1592
|
+
const cancelPendingRequest = async () => {
|
|
1593
|
+
if (!requestCreated || requestResolved)
|
|
1594
|
+
return;
|
|
1595
|
+
try {
|
|
1596
|
+
await this.apiClient.consumeRuntimeCardResponse({
|
|
1597
|
+
conversationId,
|
|
1598
|
+
cardId,
|
|
1599
|
+
cancel: true,
|
|
1600
|
+
});
|
|
1601
|
+
requestResolved = true;
|
|
1602
|
+
}
|
|
1603
|
+
catch { }
|
|
1604
|
+
};
|
|
1605
|
+
shouldPersistTurnState = true;
|
|
1606
|
+
try {
|
|
1607
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
|
|
1608
|
+
requestCreated = true;
|
|
1609
|
+
try {
|
|
1610
|
+
await turnOutput.addBlock({
|
|
1611
|
+
id: `card:${cardId}`,
|
|
1612
|
+
kind: 'input',
|
|
1613
|
+
status: 'pending',
|
|
1614
|
+
title: request.card.title,
|
|
1615
|
+
summary: request.card.template ?? 'runtime card',
|
|
1616
|
+
});
|
|
1617
|
+
await turnOutput.waitingInput();
|
|
1618
|
+
}
|
|
1619
|
+
catch { }
|
|
1620
|
+
await writeTurn('waiting_input');
|
|
1621
|
+
try {
|
|
1622
|
+
await this.apiClient.setTyping(conversationId, false);
|
|
1623
|
+
}
|
|
1624
|
+
catch { }
|
|
1625
|
+
while (Date.now() < expiresAtMs) {
|
|
1626
|
+
throwIfAborted();
|
|
1627
|
+
try {
|
|
1628
|
+
const response = await this.apiClient.consumeRuntimeCardResponse({
|
|
1629
|
+
conversationId,
|
|
1630
|
+
cardId,
|
|
1631
|
+
});
|
|
1632
|
+
if (response.status === 'submitted') {
|
|
1633
|
+
requestResolved = true;
|
|
1634
|
+
result = {
|
|
1635
|
+
status: 'submitted',
|
|
1636
|
+
cardId,
|
|
1637
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1638
|
+
...(response.values ? { values: response.values } : {}),
|
|
1639
|
+
};
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1643
|
+
requestResolved = true;
|
|
1644
|
+
result = { status: response.status, cardId };
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
catch {
|
|
1649
|
+
// Keep waiting; the control path is the authoritative source and
|
|
1650
|
+
// transient consume failures should not leak response values.
|
|
1651
|
+
}
|
|
1652
|
+
await sleepWithAbort(Math.min(RUNTIME_INPUT_POLL_MS, Math.max(1, expiresAtMs - Date.now())), abortController.signal);
|
|
1653
|
+
}
|
|
1654
|
+
if (!requestResolved) {
|
|
1655
|
+
try {
|
|
1656
|
+
const response = await this.apiClient.consumeRuntimeCardResponse({
|
|
1657
|
+
conversationId,
|
|
1658
|
+
cardId,
|
|
1659
|
+
});
|
|
1660
|
+
if (response.status === 'submitted') {
|
|
1661
|
+
result = {
|
|
1662
|
+
status: 'submitted',
|
|
1663
|
+
cardId,
|
|
1664
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1665
|
+
...(response.values ? { values: response.values } : {}),
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1669
|
+
result = { status: response.status, cardId };
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
catch { }
|
|
1673
|
+
requestResolved = true;
|
|
1674
|
+
}
|
|
1675
|
+
// The interactive path never yields 'displayed' (that returns early via
|
|
1676
|
+
// sendCard); narrow for buildRuntimeCardOutcome's resolution status.
|
|
1677
|
+
const resolutionStatus = result.status === 'displayed' ? 'timeout' : result.status;
|
|
1678
|
+
const outcome = buildRuntimeCardOutcome(cardId, resolutionStatus, {
|
|
1679
|
+
reason: resolutionStatus,
|
|
1680
|
+
});
|
|
1681
|
+
await this.apiClient.sendMessage(conversationId, outcome.text, {
|
|
1682
|
+
metadata: {
|
|
1683
|
+
...outcome.metadata,
|
|
1684
|
+
turnId: request.turnId ?? turnId,
|
|
1685
|
+
turnSemantics: 'control',
|
|
1686
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1687
|
+
},
|
|
1688
|
+
});
|
|
1689
|
+
throwIfAborted();
|
|
1690
|
+
shouldPersistTurnState = false;
|
|
1691
|
+
try {
|
|
1692
|
+
await turnOutput.completeBlock(`card:${cardId}`, {
|
|
1693
|
+
summary: `Card ${result.status}`,
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
catch { }
|
|
1697
|
+
try {
|
|
1698
|
+
await this.apiClient.setTyping(conversationId, true, 'thinking');
|
|
1699
|
+
}
|
|
1700
|
+
catch { }
|
|
1701
|
+
await setLiveState('thinking', 'Thinking...', 'thinking');
|
|
1702
|
+
return result;
|
|
1703
|
+
}
|
|
1704
|
+
catch (error) {
|
|
1705
|
+
const shouldSendInterruptedOutcome = requestCreated && !requestResolved;
|
|
1706
|
+
await cancelPendingRequest();
|
|
1707
|
+
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1708
|
+
if (shouldSendInterruptedOutcome) {
|
|
1709
|
+
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1710
|
+
await this.apiClient.sendMessage(conversationId, outcome.text, {
|
|
1711
|
+
metadata: {
|
|
1712
|
+
...outcome.metadata,
|
|
1713
|
+
turnId: request.turnId ?? turnId,
|
|
1714
|
+
turnSemantics: 'control',
|
|
1715
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1716
|
+
},
|
|
1717
|
+
}).catch(() => { });
|
|
1718
|
+
}
|
|
1719
|
+
throw error;
|
|
1720
|
+
}
|
|
1486
1721
|
shouldPersistTurnState = false;
|
|
1487
1722
|
return result;
|
|
1488
1723
|
}
|
|
@@ -1496,6 +1731,7 @@ export class CanonAgent {
|
|
|
1496
1731
|
catch { }
|
|
1497
1732
|
throwIfAborted();
|
|
1498
1733
|
try {
|
|
1734
|
+
const turnTrail = turnOutput.getFinalTrail();
|
|
1499
1735
|
const result = await sendMediaFileMessage(this.apiClient, conversationId, filePath, text, {
|
|
1500
1736
|
...(options?.replyTo ? { replyTo: options.replyTo } : {}),
|
|
1501
1737
|
...(options?.replyToPosition != null
|
|
@@ -1509,7 +1745,7 @@ export class CanonAgent {
|
|
|
1509
1745
|
...(options?.metadata ?? {}),
|
|
1510
1746
|
turnId,
|
|
1511
1747
|
turnSemantics: 'turn_complete',
|
|
1512
|
-
|
|
1748
|
+
...(turnTrail.length > 0 ? { turnTrail } : {}),
|
|
1513
1749
|
},
|
|
1514
1750
|
...(options?.fileName ? { fileName: options.fileName } : {}),
|
|
1515
1751
|
...(options?.mimeType ? { mimeType: options.mimeType } : {}),
|
|
@@ -1550,6 +1786,8 @@ export class CanonAgent {
|
|
|
1550
1786
|
provenance,
|
|
1551
1787
|
requestApproval,
|
|
1552
1788
|
requestRuntimeInput,
|
|
1789
|
+
requestCard,
|
|
1790
|
+
sendCard,
|
|
1553
1791
|
abortSignal: abortController.signal,
|
|
1554
1792
|
media: {
|
|
1555
1793
|
materialize: (message = hydratedMessages[hydratedMessages.length - 1], options) => {
|
|
@@ -1595,6 +1833,22 @@ export class CanonAgent {
|
|
|
1595
1833
|
turnOutput.appendBlock(block);
|
|
1596
1834
|
void writeTurn('streaming');
|
|
1597
1835
|
},
|
|
1836
|
+
addBlock: async (block) => {
|
|
1837
|
+
throwIfAborted();
|
|
1838
|
+
return turnOutput.addBlock(block);
|
|
1839
|
+
},
|
|
1840
|
+
updateBlock: async (id, patch) => {
|
|
1841
|
+
throwIfAborted();
|
|
1842
|
+
return turnOutput.updateBlock(id, patch);
|
|
1843
|
+
},
|
|
1844
|
+
completeBlock: async (id, patch) => {
|
|
1845
|
+
throwIfAborted();
|
|
1846
|
+
return turnOutput.completeBlock(id, patch);
|
|
1847
|
+
},
|
|
1848
|
+
failBlock: async (id, patch) => {
|
|
1849
|
+
throwIfAborted();
|
|
1850
|
+
return turnOutput.failBlock(id, patch);
|
|
1851
|
+
},
|
|
1598
1852
|
replaceSnapshot: async (text) => {
|
|
1599
1853
|
await setLiveState('streaming', text, 'streaming');
|
|
1600
1854
|
},
|
|
@@ -1605,7 +1859,14 @@ export class CanonAgent {
|
|
|
1605
1859
|
await turnOutput.clear();
|
|
1606
1860
|
},
|
|
1607
1861
|
setTool: async (text) => {
|
|
1608
|
-
await
|
|
1862
|
+
await writeTurn('tool');
|
|
1863
|
+
await turnOutput.addBlock({
|
|
1864
|
+
id: `tool:${Date.now()}`,
|
|
1865
|
+
kind: 'tool',
|
|
1866
|
+
status: 'running',
|
|
1867
|
+
title: text,
|
|
1868
|
+
});
|
|
1869
|
+
await turnOutput.setStatus('tool');
|
|
1609
1870
|
},
|
|
1610
1871
|
setWaitingInput: async (text) => {
|
|
1611
1872
|
shouldPersistTurnState = true;
|
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';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RuntimeCardNativeMetadata, RuntimeCardV1 } from '@canonmsg/core';
|
|
2
|
+
import type { RuntimeCardRequest } from './types';
|
|
3
|
+
/** Arguments passed to `CanonClient.createRuntimeCardRequest`. */
|
|
4
|
+
export interface RuntimeCardCreateArgs {
|
|
5
|
+
conversationId: string;
|
|
6
|
+
card: RuntimeCardV1;
|
|
7
|
+
cardId: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
responseUserId?: string;
|
|
10
|
+
runtimeId?: string;
|
|
11
|
+
turnId?: string;
|
|
12
|
+
native?: RuntimeCardNativeMetadata;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build the `createRuntimeCardRequest` payload shared by `sendCard` (display)
|
|
16
|
+
* and `requestCard` (interactive).
|
|
17
|
+
*
|
|
18
|
+
* `responseUserId` is OMITTED unless the developer supplied one, so the Canon
|
|
19
|
+
* backend infers a reachable responder (the owner when they are a conversation
|
|
20
|
+
* member, otherwise the sole other member). Pre-filling the owner here would
|
|
21
|
+
* break agent-to-user DMs where the owner is not a member of the conversation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildRuntimeCardCreateArgs(input: {
|
|
24
|
+
request: RuntimeCardRequest;
|
|
25
|
+
conversationId: string;
|
|
26
|
+
cardId: string;
|
|
27
|
+
fallbackTurnId?: string;
|
|
28
|
+
expiresAtMs: number;
|
|
29
|
+
}): RuntimeCardCreateArgs;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the `createRuntimeCardRequest` payload shared by `sendCard` (display)
|
|
3
|
+
* and `requestCard` (interactive).
|
|
4
|
+
*
|
|
5
|
+
* `responseUserId` is OMITTED unless the developer supplied one, so the Canon
|
|
6
|
+
* backend infers a reachable responder (the owner when they are a conversation
|
|
7
|
+
* member, otherwise the sole other member). Pre-filling the owner here would
|
|
8
|
+
* break agent-to-user DMs where the owner is not a member of the conversation.
|
|
9
|
+
*/
|
|
10
|
+
export function buildRuntimeCardCreateArgs(input) {
|
|
11
|
+
const { request, conversationId, cardId, fallbackTurnId, expiresAtMs } = input;
|
|
12
|
+
return {
|
|
13
|
+
conversationId,
|
|
14
|
+
cardId,
|
|
15
|
+
card: { ...request.card, cardId },
|
|
16
|
+
expiresAt: expiresAtMs,
|
|
17
|
+
...(request.responseUserId ? { responseUserId: request.responseUserId } : {}),
|
|
18
|
+
...(request.runtimeId ? { runtimeId: request.runtimeId } : {}),
|
|
19
|
+
turnId: request.turnId ?? fallbackTurnId,
|
|
20
|
+
...(request.native ? { native: request.native } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
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, RuntimeCardNativeMetadata, RuntimeCardV1, 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, RuntimeCardNativeMetadata, RuntimeCardV1, 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
|
/**
|
|
@@ -33,6 +33,10 @@ export interface TurnController {
|
|
|
33
33
|
setStreaming: (text: string) => Promise<void>;
|
|
34
34
|
appendDelta: (delta: string) => void;
|
|
35
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>;
|
|
36
40
|
replaceSnapshot: (text: string) => Promise<void>;
|
|
37
41
|
flush: () => Promise<void>;
|
|
38
42
|
clear: () => Promise<void>;
|
|
@@ -66,6 +70,7 @@ export interface RuntimeInputRequest {
|
|
|
66
70
|
title: string;
|
|
67
71
|
prompt: string;
|
|
68
72
|
choices?: RuntimeInputChoice[];
|
|
73
|
+
questions?: RuntimeInputQuestion[];
|
|
69
74
|
secretName?: string;
|
|
70
75
|
sensitive?: boolean;
|
|
71
76
|
runtimeId?: string;
|
|
@@ -77,8 +82,25 @@ export interface RuntimeInputRequest {
|
|
|
77
82
|
export interface RuntimeInputResult {
|
|
78
83
|
status: 'submitted' | 'cancelled' | 'timeout';
|
|
79
84
|
value?: string;
|
|
85
|
+
answers?: RuntimeInputAnswers;
|
|
80
86
|
inputId: string;
|
|
81
87
|
}
|
|
88
|
+
export interface RuntimeCardRequest {
|
|
89
|
+
card: RuntimeCardV1;
|
|
90
|
+
cardId?: string;
|
|
91
|
+
expiresAt?: string | number | Date;
|
|
92
|
+
responseUserId?: string;
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
runtimeId?: string;
|
|
95
|
+
turnId?: string;
|
|
96
|
+
native?: RuntimeCardNativeMetadata;
|
|
97
|
+
}
|
|
98
|
+
export interface RuntimeCardResult {
|
|
99
|
+
status: 'submitted' | 'cancelled' | 'timeout' | 'displayed';
|
|
100
|
+
cardId: string;
|
|
101
|
+
actionId?: string;
|
|
102
|
+
values?: Record<string, unknown>;
|
|
103
|
+
}
|
|
82
104
|
export interface MessageHandlerContext {
|
|
83
105
|
messages: CanonMessage[];
|
|
84
106
|
history: CanonMessage[];
|
|
@@ -138,6 +160,19 @@ export interface MessageHandlerContext {
|
|
|
138
160
|
* never persisted in Canon message metadata.
|
|
139
161
|
*/
|
|
140
162
|
requestRuntimeInput: (request: RuntimeInputRequest) => Promise<RuntimeInputResult>;
|
|
163
|
+
/**
|
|
164
|
+
* Ask the agent owner to review/respond to a generic rich card. The visible
|
|
165
|
+
* card document is redacted for presentation; raw response values return
|
|
166
|
+
* only to the calling runtime through Canon's control path.
|
|
167
|
+
*/
|
|
168
|
+
requestCard: (request: RuntimeCardRequest) => Promise<RuntimeCardResult>;
|
|
169
|
+
/**
|
|
170
|
+
* Post a fire-and-forget display/report card and return immediately with
|
|
171
|
+
* `{ status: 'displayed' }` — no waiting-input lifecycle. Use requestCard for
|
|
172
|
+
* cards that expect a response. (requestCard also auto-detects action-less
|
|
173
|
+
* cards and delegates here.)
|
|
174
|
+
*/
|
|
175
|
+
sendCard: (request: RuntimeCardRequest) => Promise<RuntimeCardResult>;
|
|
141
176
|
/** Canon-managed local media access for the current conversation. */
|
|
142
177
|
media: {
|
|
143
178
|
materialize: (message?: CanonMessage, options?: Omit<MaterializeMediaOptions, 'agentId' | 'conversationId' | 'messageId'>) => Promise<MaterializedCanonAttachment[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.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": "^1.2.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|