@canonmsg/agent-sdk 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canon-agent.d.ts +1 -0
- package/dist/canon-agent.js +227 -14
- package/dist/runtime-card.d.ts +29 -0
- package/dist/runtime-card.js +22 -0
- package/dist/types.d.ts +31 -2
- package/package.json +2 -2
package/dist/canon-agent.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare class CanonAgent {
|
|
|
55
55
|
private readonly activeTurns;
|
|
56
56
|
private readonly conversationMemberIds;
|
|
57
57
|
private readonly pendingMembershipChanges;
|
|
58
|
+
private readonly typingSignals;
|
|
58
59
|
private sseConnectedLogged;
|
|
59
60
|
constructor(options: CanonAgentOptions);
|
|
60
61
|
private ensureApprovalManager;
|
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, createTypingStatusPublisher, 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'
|
|
@@ -240,6 +259,7 @@ export class CanonAgent {
|
|
|
240
259
|
activeTurns = new Map();
|
|
241
260
|
conversationMemberIds = new Map();
|
|
242
261
|
pendingMembershipChanges = new Map();
|
|
262
|
+
typingSignals;
|
|
243
263
|
sseConnectedLogged = false;
|
|
244
264
|
constructor(options) {
|
|
245
265
|
this.options = {
|
|
@@ -252,6 +272,11 @@ export class CanonAgent {
|
|
|
252
272
|
...options,
|
|
253
273
|
};
|
|
254
274
|
this.apiClient = new CanonClient(this.options.apiKey, this.options.baseUrl);
|
|
275
|
+
this.typingSignals = createTypingStatusPublisher({
|
|
276
|
+
setTyping: (conversationId, typing, status) => status
|
|
277
|
+
? this.apiClient.setTyping(conversationId, typing, status)
|
|
278
|
+
: this.apiClient.setTyping(conversationId, typing),
|
|
279
|
+
});
|
|
255
280
|
this.authManager = new AuthManager(this.apiClient);
|
|
256
281
|
this.debouncer = new Debouncer(this.options.debounceMs);
|
|
257
282
|
const apiClient = this.apiClient;
|
|
@@ -983,7 +1008,7 @@ export class CanonAgent {
|
|
|
983
1008
|
}
|
|
984
1009
|
await Promise.all([
|
|
985
1010
|
this.apiClient.clearStreaming(conversationId).catch(() => { }),
|
|
986
|
-
this.
|
|
1011
|
+
this.typingSignals.clear(conversationId).catch(() => { }),
|
|
987
1012
|
]);
|
|
988
1013
|
}
|
|
989
1014
|
abortActiveTurns(conversationId) {
|
|
@@ -1130,14 +1155,13 @@ export class CanonAgent {
|
|
|
1130
1155
|
};
|
|
1131
1156
|
// Show thinking indicator and keep it alive (5s client-side expiry)
|
|
1132
1157
|
try {
|
|
1133
|
-
await this.
|
|
1158
|
+
await this.typingSignals.start(conversationId, 'thinking');
|
|
1134
1159
|
}
|
|
1135
1160
|
catch {
|
|
1136
1161
|
// Non-critical
|
|
1137
1162
|
}
|
|
1138
1163
|
await setLiveState('thinking', 'Thinking...', 'thinking');
|
|
1139
1164
|
const thinkingKeepalive = setInterval(() => {
|
|
1140
|
-
this.apiClient.setTyping(conversationId, true, 'thinking').catch(() => { });
|
|
1141
1165
|
if (turnState === 'thinking') {
|
|
1142
1166
|
turnOutput.setStatus('thinking', 'Thinking...').catch(() => { });
|
|
1143
1167
|
}
|
|
@@ -1170,7 +1194,7 @@ export class CanonAgent {
|
|
|
1170
1194
|
const replyFinal = async (text, options) => {
|
|
1171
1195
|
throwIfAborted();
|
|
1172
1196
|
try {
|
|
1173
|
-
await this.
|
|
1197
|
+
await this.typingSignals.start(conversationId, 'typing');
|
|
1174
1198
|
}
|
|
1175
1199
|
catch { }
|
|
1176
1200
|
throwIfAborted();
|
|
@@ -1187,7 +1211,7 @@ export class CanonAgent {
|
|
|
1187
1211
|
});
|
|
1188
1212
|
await sleep(FINAL_MESSAGE_HANDOFF_MS);
|
|
1189
1213
|
try {
|
|
1190
|
-
await this.
|
|
1214
|
+
await this.typingSignals.clear(conversationId);
|
|
1191
1215
|
}
|
|
1192
1216
|
catch { }
|
|
1193
1217
|
return result;
|
|
@@ -1336,7 +1360,7 @@ export class CanonAgent {
|
|
|
1336
1360
|
catch { }
|
|
1337
1361
|
await writeTurn('waiting_input');
|
|
1338
1362
|
try {
|
|
1339
|
-
await this.
|
|
1363
|
+
await this.typingSignals.clear(conversationId);
|
|
1340
1364
|
}
|
|
1341
1365
|
catch { }
|
|
1342
1366
|
const result = await manager.requestApproval(conversationId, request.toolName, request.toolInput ?? {}, {
|
|
@@ -1360,7 +1384,7 @@ export class CanonAgent {
|
|
|
1360
1384
|
}
|
|
1361
1385
|
catch { }
|
|
1362
1386
|
try {
|
|
1363
|
-
await this.
|
|
1387
|
+
await this.typingSignals.start(conversationId, 'thinking');
|
|
1364
1388
|
}
|
|
1365
1389
|
catch { }
|
|
1366
1390
|
await setLiveState('thinking', 'Thinking...', 'thinking');
|
|
@@ -1427,7 +1451,7 @@ export class CanonAgent {
|
|
|
1427
1451
|
catch { }
|
|
1428
1452
|
await writeTurn('waiting_input');
|
|
1429
1453
|
try {
|
|
1430
|
-
await this.
|
|
1454
|
+
await this.typingSignals.clear(conversationId);
|
|
1431
1455
|
}
|
|
1432
1456
|
catch { }
|
|
1433
1457
|
while (Date.now() < expiresAtMs) {
|
|
@@ -1501,7 +1525,7 @@ export class CanonAgent {
|
|
|
1501
1525
|
}
|
|
1502
1526
|
catch { }
|
|
1503
1527
|
try {
|
|
1504
|
-
await this.
|
|
1528
|
+
await this.typingSignals.start(conversationId, 'thinking');
|
|
1505
1529
|
}
|
|
1506
1530
|
catch { }
|
|
1507
1531
|
await setLiveState('thinking', 'Thinking...', 'thinking');
|
|
@@ -1512,6 +1536,193 @@ export class CanonAgent {
|
|
|
1512
1536
|
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1513
1537
|
throw error;
|
|
1514
1538
|
}
|
|
1539
|
+
if (!requestCreated) {
|
|
1540
|
+
throw error;
|
|
1541
|
+
}
|
|
1542
|
+
shouldPersistTurnState = false;
|
|
1543
|
+
return result;
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
const sendCard = async (request) => {
|
|
1547
|
+
throwIfAborted();
|
|
1548
|
+
const cardId = safeRuntimeCardId(request.cardId ?? request.card.cardId);
|
|
1549
|
+
const explicitExpiresAt = request.expiresAt instanceof Date
|
|
1550
|
+
? request.expiresAt.getTime()
|
|
1551
|
+
: typeof request.expiresAt === 'number'
|
|
1552
|
+
? request.expiresAt
|
|
1553
|
+
: typeof request.expiresAt === 'string'
|
|
1554
|
+
? Date.parse(request.expiresAt)
|
|
1555
|
+
: null;
|
|
1556
|
+
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1557
|
+
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1558
|
+
? explicitExpiresAt
|
|
1559
|
+
: Date.now() + timeoutMs;
|
|
1560
|
+
// Fire-and-forget: post the durable card and return. The backend treats an
|
|
1561
|
+
// action-less card as display (no pending state, no response expected).
|
|
1562
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
|
|
1563
|
+
try {
|
|
1564
|
+
await turnOutput.addBlock({
|
|
1565
|
+
id: `card:${cardId}`,
|
|
1566
|
+
kind: 'status',
|
|
1567
|
+
status: 'completed',
|
|
1568
|
+
title: request.card.title,
|
|
1569
|
+
summary: request.card.template ?? 'runtime card',
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
catch { }
|
|
1573
|
+
return { status: 'displayed', cardId };
|
|
1574
|
+
};
|
|
1575
|
+
const requestCard = async (request) => {
|
|
1576
|
+
throwIfAborted();
|
|
1577
|
+
// Action-less cards are fire-and-forget display/report cards — never block.
|
|
1578
|
+
const hasActions = Array.isArray(request.card.blocks)
|
|
1579
|
+
&& request.card.blocks.some((block) => block.kind === 'actions');
|
|
1580
|
+
if (!hasActions)
|
|
1581
|
+
return sendCard(request);
|
|
1582
|
+
const cardId = safeRuntimeCardId(request.cardId ?? request.card.cardId);
|
|
1583
|
+
const explicitExpiresAt = request.expiresAt instanceof Date
|
|
1584
|
+
? request.expiresAt.getTime()
|
|
1585
|
+
: typeof request.expiresAt === 'number'
|
|
1586
|
+
? request.expiresAt
|
|
1587
|
+
: typeof request.expiresAt === 'string'
|
|
1588
|
+
? Date.parse(request.expiresAt)
|
|
1589
|
+
: null;
|
|
1590
|
+
const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
|
|
1591
|
+
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1592
|
+
? explicitExpiresAt
|
|
1593
|
+
: Date.now() + timeoutMs;
|
|
1594
|
+
let result = { status: 'timeout', cardId };
|
|
1595
|
+
let requestCreated = false;
|
|
1596
|
+
let requestResolved = false;
|
|
1597
|
+
const cancelPendingRequest = async () => {
|
|
1598
|
+
if (!requestCreated || requestResolved)
|
|
1599
|
+
return;
|
|
1600
|
+
try {
|
|
1601
|
+
await this.apiClient.consumeRuntimeCardResponse({
|
|
1602
|
+
conversationId,
|
|
1603
|
+
cardId,
|
|
1604
|
+
cancel: true,
|
|
1605
|
+
});
|
|
1606
|
+
requestResolved = true;
|
|
1607
|
+
}
|
|
1608
|
+
catch { }
|
|
1609
|
+
};
|
|
1610
|
+
shouldPersistTurnState = true;
|
|
1611
|
+
try {
|
|
1612
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
|
|
1613
|
+
requestCreated = true;
|
|
1614
|
+
try {
|
|
1615
|
+
await turnOutput.addBlock({
|
|
1616
|
+
id: `card:${cardId}`,
|
|
1617
|
+
kind: 'input',
|
|
1618
|
+
status: 'pending',
|
|
1619
|
+
title: request.card.title,
|
|
1620
|
+
summary: request.card.template ?? 'runtime card',
|
|
1621
|
+
});
|
|
1622
|
+
await turnOutput.waitingInput();
|
|
1623
|
+
}
|
|
1624
|
+
catch { }
|
|
1625
|
+
await writeTurn('waiting_input');
|
|
1626
|
+
try {
|
|
1627
|
+
await this.typingSignals.clear(conversationId);
|
|
1628
|
+
}
|
|
1629
|
+
catch { }
|
|
1630
|
+
while (Date.now() < expiresAtMs) {
|
|
1631
|
+
throwIfAborted();
|
|
1632
|
+
try {
|
|
1633
|
+
const response = await this.apiClient.consumeRuntimeCardResponse({
|
|
1634
|
+
conversationId,
|
|
1635
|
+
cardId,
|
|
1636
|
+
});
|
|
1637
|
+
if (response.status === 'submitted') {
|
|
1638
|
+
requestResolved = true;
|
|
1639
|
+
result = {
|
|
1640
|
+
status: 'submitted',
|
|
1641
|
+
cardId,
|
|
1642
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1643
|
+
...(response.values ? { values: response.values } : {}),
|
|
1644
|
+
};
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1648
|
+
requestResolved = true;
|
|
1649
|
+
result = { status: response.status, cardId };
|
|
1650
|
+
break;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
catch {
|
|
1654
|
+
// Keep waiting; the control path is the authoritative source and
|
|
1655
|
+
// transient consume failures should not leak response values.
|
|
1656
|
+
}
|
|
1657
|
+
await sleepWithAbort(Math.min(RUNTIME_INPUT_POLL_MS, Math.max(1, expiresAtMs - Date.now())), abortController.signal);
|
|
1658
|
+
}
|
|
1659
|
+
if (!requestResolved) {
|
|
1660
|
+
try {
|
|
1661
|
+
const response = await this.apiClient.consumeRuntimeCardResponse({
|
|
1662
|
+
conversationId,
|
|
1663
|
+
cardId,
|
|
1664
|
+
});
|
|
1665
|
+
if (response.status === 'submitted') {
|
|
1666
|
+
result = {
|
|
1667
|
+
status: 'submitted',
|
|
1668
|
+
cardId,
|
|
1669
|
+
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1670
|
+
...(response.values ? { values: response.values } : {}),
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1674
|
+
result = { status: response.status, cardId };
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
catch { }
|
|
1678
|
+
requestResolved = true;
|
|
1679
|
+
}
|
|
1680
|
+
// The interactive path never yields 'displayed' (that returns early via
|
|
1681
|
+
// sendCard); narrow for buildRuntimeCardOutcome's resolution status.
|
|
1682
|
+
const resolutionStatus = result.status === 'displayed' ? 'timeout' : result.status;
|
|
1683
|
+
const outcome = buildRuntimeCardOutcome(cardId, resolutionStatus, {
|
|
1684
|
+
reason: resolutionStatus,
|
|
1685
|
+
});
|
|
1686
|
+
await this.apiClient.sendMessage(conversationId, outcome.text, {
|
|
1687
|
+
metadata: {
|
|
1688
|
+
...outcome.metadata,
|
|
1689
|
+
turnId: request.turnId ?? turnId,
|
|
1690
|
+
turnSemantics: 'control',
|
|
1691
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1692
|
+
},
|
|
1693
|
+
});
|
|
1694
|
+
throwIfAborted();
|
|
1695
|
+
shouldPersistTurnState = false;
|
|
1696
|
+
try {
|
|
1697
|
+
await turnOutput.completeBlock(`card:${cardId}`, {
|
|
1698
|
+
summary: `Card ${result.status}`,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
catch { }
|
|
1702
|
+
try {
|
|
1703
|
+
await this.typingSignals.start(conversationId, 'thinking');
|
|
1704
|
+
}
|
|
1705
|
+
catch { }
|
|
1706
|
+
await setLiveState('thinking', 'Thinking...', 'thinking');
|
|
1707
|
+
return result;
|
|
1708
|
+
}
|
|
1709
|
+
catch (error) {
|
|
1710
|
+
const shouldSendInterruptedOutcome = requestCreated && !requestResolved;
|
|
1711
|
+
await cancelPendingRequest();
|
|
1712
|
+
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1713
|
+
if (shouldSendInterruptedOutcome) {
|
|
1714
|
+
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1715
|
+
await this.apiClient.sendMessage(conversationId, outcome.text, {
|
|
1716
|
+
metadata: {
|
|
1717
|
+
...outcome.metadata,
|
|
1718
|
+
turnId: request.turnId ?? turnId,
|
|
1719
|
+
turnSemantics: 'control',
|
|
1720
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1721
|
+
},
|
|
1722
|
+
}).catch(() => { });
|
|
1723
|
+
}
|
|
1724
|
+
throw error;
|
|
1725
|
+
}
|
|
1515
1726
|
shouldPersistTurnState = false;
|
|
1516
1727
|
return result;
|
|
1517
1728
|
}
|
|
@@ -1520,7 +1731,7 @@ export class CanonAgent {
|
|
|
1520
1731
|
const replyWithFile = async (filePath, text = '', options) => {
|
|
1521
1732
|
throwIfAborted();
|
|
1522
1733
|
try {
|
|
1523
|
-
await this.
|
|
1734
|
+
await this.typingSignals.start(conversationId, 'typing');
|
|
1524
1735
|
}
|
|
1525
1736
|
catch { }
|
|
1526
1737
|
throwIfAborted();
|
|
@@ -1550,7 +1761,7 @@ export class CanonAgent {
|
|
|
1550
1761
|
}
|
|
1551
1762
|
finally {
|
|
1552
1763
|
try {
|
|
1553
|
-
await this.
|
|
1764
|
+
await this.typingSignals.clear(conversationId);
|
|
1554
1765
|
}
|
|
1555
1766
|
catch { }
|
|
1556
1767
|
}
|
|
@@ -1580,6 +1791,8 @@ export class CanonAgent {
|
|
|
1580
1791
|
provenance,
|
|
1581
1792
|
requestApproval,
|
|
1582
1793
|
requestRuntimeInput,
|
|
1794
|
+
requestCard,
|
|
1795
|
+
sendCard,
|
|
1583
1796
|
abortSignal: abortController.signal,
|
|
1584
1797
|
media: {
|
|
1585
1798
|
materialize: (message = hydratedMessages[hydratedMessages.length - 1], options) => {
|
|
@@ -1668,7 +1881,7 @@ export class CanonAgent {
|
|
|
1668
1881
|
catch { }
|
|
1669
1882
|
await writeTurn('waiting_input');
|
|
1670
1883
|
try {
|
|
1671
|
-
await this.
|
|
1884
|
+
await this.typingSignals.clear(conversationId);
|
|
1672
1885
|
}
|
|
1673
1886
|
catch { }
|
|
1674
1887
|
if (text) {
|
|
@@ -1715,7 +1928,7 @@ export class CanonAgent {
|
|
|
1715
1928
|
clearInterval(thinkingKeepalive);
|
|
1716
1929
|
// Always clear typing when done
|
|
1717
1930
|
try {
|
|
1718
|
-
await this.
|
|
1931
|
+
await this.typingSignals.clear(conversationId);
|
|
1719
1932
|
}
|
|
1720
1933
|
catch { }
|
|
1721
1934
|
try {
|
|
@@ -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/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, 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';
|
|
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
|
/**
|
|
@@ -85,6 +85,22 @@ export interface RuntimeInputResult {
|
|
|
85
85
|
answers?: RuntimeInputAnswers;
|
|
86
86
|
inputId: string;
|
|
87
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
|
+
}
|
|
88
104
|
export interface MessageHandlerContext {
|
|
89
105
|
messages: CanonMessage[];
|
|
90
106
|
history: CanonMessage[];
|
|
@@ -144,6 +160,19 @@ export interface MessageHandlerContext {
|
|
|
144
160
|
* never persisted in Canon message metadata.
|
|
145
161
|
*/
|
|
146
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>;
|
|
147
176
|
/** Canon-managed local media access for the current conversation. */
|
|
148
177
|
media: {
|
|
149
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": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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": "^1.
|
|
31
|
+
"@canonmsg/core": "^1.3.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|