@canonmsg/backend-contracts 0.2.3 → 1.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/dist/cjs/contactRequest.js +16 -5
- package/dist/cjs/message.js +21 -19
- package/dist/cjs/turnProtocol.js +38 -7
- package/dist/contactRequest.d.ts +2 -2
- package/dist/contactRequest.js +16 -5
- package/dist/message.d.ts +0 -2
- package/dist/message.js +22 -20
- package/dist/turnProtocol.d.ts +7 -4
- package/dist/turnProtocol.js +35 -7
- package/package.json +1 -1
|
@@ -33,6 +33,19 @@ function serializeContactRequest(requestId, data) {
|
|
|
33
33
|
if (typeof data.targetId !== 'string' || data.targetId.length === 0) {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
+
if (typeof data.targetName !== 'string' || data.targetName.trim().length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (data.targetAvatarUrl !== null && data.targetAvatarUrl !== undefined && typeof data.targetAvatarUrl !== 'string') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (data.targetUserType !== 'human' && data.targetUserType !== 'ai_agent') {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (data.targetUserType === 'ai_agent'
|
|
46
|
+
&& (typeof data.targetOwnerId !== 'string' || data.targetOwnerId.length === 0)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
36
49
|
const kindValue = typeof data.kind === 'string' ? data.kind : null;
|
|
37
50
|
if (kindValue !== 'dm' && kindValue !== 'group_invite') {
|
|
38
51
|
return null;
|
|
@@ -57,11 +70,9 @@ function serializeContactRequest(requestId, data) {
|
|
|
57
70
|
requesterName: typeof data.requesterName === 'string' ? data.requesterName : 'Unknown',
|
|
58
71
|
requesterAvatarUrl: typeof data.requesterAvatarUrl === 'string' ? data.requesterAvatarUrl : null,
|
|
59
72
|
targetId: data.targetId,
|
|
60
|
-
targetName:
|
|
73
|
+
targetName: data.targetName.trim(),
|
|
61
74
|
targetAvatarUrl: typeof data.targetAvatarUrl === 'string' ? data.targetAvatarUrl : null,
|
|
62
|
-
targetUserType: data.targetUserType
|
|
63
|
-
? data.targetUserType
|
|
64
|
-
: null,
|
|
75
|
+
targetUserType: data.targetUserType,
|
|
65
76
|
approverId: data.approverId,
|
|
66
77
|
message: typeof data.message === 'string' ? data.message : null,
|
|
67
78
|
status: normalizeStatus(data.status),
|
|
@@ -73,7 +84,7 @@ function serializeContactRequest(requestId, data) {
|
|
|
73
84
|
if (groupContext) {
|
|
74
85
|
payload.groupContext = groupContext;
|
|
75
86
|
}
|
|
76
|
-
if (
|
|
87
|
+
if (data.targetUserType === 'ai_agent') {
|
|
77
88
|
payload.targetOwnerId = data.targetOwnerId;
|
|
78
89
|
}
|
|
79
90
|
return payload;
|
package/dist/cjs/message.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serializeStoredMessage = serializeStoredMessage;
|
|
4
4
|
const media_js_1 = require("./media.js");
|
|
5
|
-
const MISSING_CREATED_AT_ISO = new Date(0).toISOString();
|
|
6
5
|
function isRecord(value) {
|
|
7
6
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
7
|
}
|
|
@@ -21,14 +20,17 @@ function normalizeTimestamp(value) {
|
|
|
21
20
|
}
|
|
22
21
|
return null;
|
|
23
22
|
}
|
|
24
|
-
function
|
|
25
|
-
const timestamp = normalizeTimestamp(value)
|
|
26
|
-
|
|
23
|
+
function requireCreatedAt(value) {
|
|
24
|
+
const timestamp = normalizeTimestamp(value);
|
|
25
|
+
if (!timestamp) {
|
|
26
|
+
throw new Error('Message is missing canonical createdAt');
|
|
27
|
+
}
|
|
28
|
+
return timestamp.toISOString();
|
|
27
29
|
}
|
|
28
|
-
function
|
|
29
|
-
if (value === '
|
|
30
|
-
return
|
|
31
|
-
|
|
30
|
+
function requireSenderType(value) {
|
|
31
|
+
if (value === 'human' || value === 'ai_agent')
|
|
32
|
+
return value;
|
|
33
|
+
throw new Error('Message is missing canonical senderType');
|
|
32
34
|
}
|
|
33
35
|
function normalizeAgentClientType(value) {
|
|
34
36
|
if (value === 'claude-code'
|
|
@@ -40,7 +42,7 @@ function normalizeAgentClientType(value) {
|
|
|
40
42
|
}
|
|
41
43
|
return undefined;
|
|
42
44
|
}
|
|
43
|
-
function normalizeContentType(value
|
|
45
|
+
function normalizeContentType(value) {
|
|
44
46
|
if (value === 'text'
|
|
45
47
|
|| value === 'image'
|
|
46
48
|
|| value === 'audio'
|
|
@@ -48,10 +50,7 @@ function normalizeContentType(value, attachments) {
|
|
|
48
50
|
|| value === 'contact_card') {
|
|
49
51
|
return value;
|
|
50
52
|
}
|
|
51
|
-
|
|
52
|
-
if (primary === 'image' || primary === 'audio' || primary === 'file')
|
|
53
|
-
return primary;
|
|
54
|
-
return 'text';
|
|
53
|
+
throw new Error('Message is missing canonical contentType');
|
|
55
54
|
}
|
|
56
55
|
function normalizeForwardedFrom(value) {
|
|
57
56
|
if (!isRecord(value))
|
|
@@ -96,22 +95,25 @@ function normalizeContactCard(value) {
|
|
|
96
95
|
}
|
|
97
96
|
function serializeStoredMessage(input) {
|
|
98
97
|
const { data } = input;
|
|
99
|
-
const attachments =
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
const attachments = data.attachments === undefined
|
|
99
|
+
? []
|
|
100
|
+
: (0, media_js_1.normalizeStoredAttachments)(data.attachments);
|
|
101
|
+
if (!attachments) {
|
|
102
|
+
throw new Error('Message has malformed attachments');
|
|
103
|
+
}
|
|
102
104
|
const result = {
|
|
103
105
|
id: input.id,
|
|
104
106
|
senderId: typeof data.senderId === 'string' ? data.senderId : '',
|
|
105
|
-
senderType:
|
|
107
|
+
senderType: requireSenderType(data.senderType),
|
|
106
108
|
isOwner: input.isOwner,
|
|
107
|
-
contentType: normalizeContentType(data.contentType
|
|
109
|
+
contentType: normalizeContentType(data.contentType),
|
|
108
110
|
text: typeof data.text === 'string' ? data.text : null,
|
|
109
111
|
attachments,
|
|
110
112
|
mentions: normalizeStringArray(data.mentions),
|
|
111
113
|
replyTo: typeof data.replyTo === 'string' ? data.replyTo : null,
|
|
112
114
|
replyToPosition: typeof data.replyToPosition === 'number' ? data.replyToPosition : null,
|
|
113
115
|
status: data.status === 'read' ? 'read' : 'sent',
|
|
114
|
-
createdAt:
|
|
116
|
+
createdAt: requireCreatedAt(data.createdAt),
|
|
115
117
|
};
|
|
116
118
|
const forwardedFrom = normalizeForwardedFrom(data.forwardedFrom);
|
|
117
119
|
if (data.forwarded === true || forwardedFrom) {
|
package/dist/cjs/turnProtocol.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WAITING_INPUT_STALE_THRESHOLD_MS = exports.ACTIVE_TURN_STALE_THRESHOLD_MS = void 0;
|
|
3
4
|
exports.normalizeTurnMetadata = normalizeTurnMetadata;
|
|
4
5
|
exports.isTurnOpen = isTurnOpen;
|
|
6
|
+
exports.getTurnStateStaleThresholdMs = getTurnStateStaleThresholdMs;
|
|
7
|
+
exports.isTurnStateStale = isTurnStateStale;
|
|
5
8
|
exports.resolveTurnMessageSemantics = resolveTurnMessageSemantics;
|
|
6
9
|
exports.shouldPromoteConversationMessage = shouldPromoteConversationMessage;
|
|
7
10
|
exports.shouldTriggerAgentTurn = shouldTriggerAgentTurn;
|
|
8
11
|
exports.normalizeRuntimeTurnState = normalizeRuntimeTurnState;
|
|
12
|
+
exports.ACTIVE_TURN_STALE_THRESHOLD_MS = 30 * 60 * 1000;
|
|
13
|
+
exports.WAITING_INPUT_STALE_THRESHOLD_MS = 12 * 60 * 60 * 1000;
|
|
9
14
|
function isRecord(value) {
|
|
10
15
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
16
|
}
|
|
@@ -31,31 +36,52 @@ function normalizeTurnMetadata(metadata) {
|
|
|
31
36
|
|| metadata.replyBehavior === 'suppress_auto_reply'
|
|
32
37
|
? metadata.replyBehavior
|
|
33
38
|
: undefined;
|
|
34
|
-
if (!turnSemantics &&
|
|
39
|
+
if (!turnSemantics && !replyBehavior) {
|
|
35
40
|
return null;
|
|
36
41
|
}
|
|
37
42
|
return {
|
|
38
43
|
...(turnSemantics ? { turnSemantics } : {}),
|
|
39
|
-
...(typeof metadata.turnComplete === 'boolean' ? { turnComplete: metadata.turnComplete } : {}),
|
|
40
44
|
...(replyBehavior ? { replyBehavior } : {}),
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
function isTurnOpen(turnState) {
|
|
44
48
|
if (!turnState)
|
|
45
49
|
return false;
|
|
46
|
-
|
|
50
|
+
const open = turnState.state !== 'idle'
|
|
47
51
|
&& turnState.state !== 'completed'
|
|
48
52
|
&& turnState.state !== 'interrupted';
|
|
53
|
+
if (!open)
|
|
54
|
+
return false;
|
|
55
|
+
return !isTurnStateStale(turnState);
|
|
56
|
+
}
|
|
57
|
+
function getTurnStateStaleThresholdMs(state) {
|
|
58
|
+
return state === 'waiting_input'
|
|
59
|
+
? exports.WAITING_INPUT_STALE_THRESHOLD_MS
|
|
60
|
+
: exports.ACTIVE_TURN_STALE_THRESHOLD_MS;
|
|
61
|
+
}
|
|
62
|
+
function isTurnStateStale(turnState) {
|
|
63
|
+
if (!turnState)
|
|
64
|
+
return false;
|
|
65
|
+
if (turnState.state !== 'thinking'
|
|
66
|
+
&& turnState.state !== 'streaming'
|
|
67
|
+
&& turnState.state !== 'tool'
|
|
68
|
+
&& turnState.state !== 'waiting_input') {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const updatedAt = turnState.turnUpdatedAt
|
|
72
|
+
?? turnState.updatedAt
|
|
73
|
+
?? turnState.openedAt;
|
|
74
|
+
if (updatedAt == null)
|
|
75
|
+
return false;
|
|
76
|
+
return Date.now() - updatedAt >= getTurnStateStaleThresholdMs(turnState.state);
|
|
49
77
|
}
|
|
50
78
|
function resolveTurnMessageSemantics(input) {
|
|
51
79
|
const turnMetadata = normalizeTurnMetadata(input.metadata);
|
|
52
80
|
if (turnMetadata?.turnSemantics)
|
|
53
81
|
return turnMetadata.turnSemantics;
|
|
54
|
-
if (turnMetadata?.turnComplete === true)
|
|
55
|
-
return 'turn_complete';
|
|
56
82
|
if (input.senderType === 'human')
|
|
57
83
|
return 'turn_complete';
|
|
58
|
-
return
|
|
84
|
+
return 'progress';
|
|
59
85
|
}
|
|
60
86
|
function shouldPromoteConversationMessage(input) {
|
|
61
87
|
if (isHiddenRuntimeCardMetadata(input.metadata)) {
|
|
@@ -84,7 +110,12 @@ function normalizeRuntimeTurnState(value) {
|
|
|
84
110
|
|| value.state === 'waiting_input'
|
|
85
111
|
|| value.state === 'completed'
|
|
86
112
|
|| value.state === 'interrupted') {
|
|
87
|
-
return {
|
|
113
|
+
return {
|
|
114
|
+
state: value.state,
|
|
115
|
+
...(typeof value.openedAt === 'number' ? { openedAt: value.openedAt } : {}),
|
|
116
|
+
...(typeof value.updatedAt === 'number' ? { updatedAt: value.updatedAt } : {}),
|
|
117
|
+
...(typeof value.turnUpdatedAt === 'number' ? { turnUpdatedAt: value.turnUpdatedAt } : {}),
|
|
118
|
+
};
|
|
88
119
|
}
|
|
89
120
|
return null;
|
|
90
121
|
}
|
package/dist/contactRequest.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ export interface SerializedContactRequest {
|
|
|
5
5
|
requesterName: string;
|
|
6
6
|
requesterAvatarUrl: string | null;
|
|
7
7
|
targetId: string;
|
|
8
|
-
targetName: string
|
|
8
|
+
targetName: string;
|
|
9
9
|
targetAvatarUrl: string | null;
|
|
10
|
-
targetUserType: 'human' | 'ai_agent'
|
|
10
|
+
targetUserType: 'human' | 'ai_agent';
|
|
11
11
|
targetOwnerId?: string | null;
|
|
12
12
|
approverId: string;
|
|
13
13
|
message: string | null;
|
package/dist/contactRequest.js
CHANGED
|
@@ -30,6 +30,19 @@ export function serializeContactRequest(requestId, data) {
|
|
|
30
30
|
if (typeof data.targetId !== 'string' || data.targetId.length === 0) {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
+
if (typeof data.targetName !== 'string' || data.targetName.trim().length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (data.targetAvatarUrl !== null && data.targetAvatarUrl !== undefined && typeof data.targetAvatarUrl !== 'string') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (data.targetUserType !== 'human' && data.targetUserType !== 'ai_agent') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (data.targetUserType === 'ai_agent'
|
|
43
|
+
&& (typeof data.targetOwnerId !== 'string' || data.targetOwnerId.length === 0)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
33
46
|
const kindValue = typeof data.kind === 'string' ? data.kind : null;
|
|
34
47
|
if (kindValue !== 'dm' && kindValue !== 'group_invite') {
|
|
35
48
|
return null;
|
|
@@ -54,11 +67,9 @@ export function serializeContactRequest(requestId, data) {
|
|
|
54
67
|
requesterName: typeof data.requesterName === 'string' ? data.requesterName : 'Unknown',
|
|
55
68
|
requesterAvatarUrl: typeof data.requesterAvatarUrl === 'string' ? data.requesterAvatarUrl : null,
|
|
56
69
|
targetId: data.targetId,
|
|
57
|
-
targetName:
|
|
70
|
+
targetName: data.targetName.trim(),
|
|
58
71
|
targetAvatarUrl: typeof data.targetAvatarUrl === 'string' ? data.targetAvatarUrl : null,
|
|
59
|
-
targetUserType: data.targetUserType
|
|
60
|
-
? data.targetUserType
|
|
61
|
-
: null,
|
|
72
|
+
targetUserType: data.targetUserType,
|
|
62
73
|
approverId: data.approverId,
|
|
63
74
|
message: typeof data.message === 'string' ? data.message : null,
|
|
64
75
|
status: normalizeStatus(data.status),
|
|
@@ -70,7 +81,7 @@ export function serializeContactRequest(requestId, data) {
|
|
|
70
81
|
if (groupContext) {
|
|
71
82
|
payload.groupContext = groupContext;
|
|
72
83
|
}
|
|
73
|
-
if (
|
|
84
|
+
if (data.targetUserType === 'ai_agent') {
|
|
74
85
|
payload.targetOwnerId = data.targetOwnerId;
|
|
75
86
|
}
|
|
76
87
|
return payload;
|
package/dist/message.d.ts
CHANGED
package/dist/message.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const MISSING_CREATED_AT_ISO = new Date(0).toISOString();
|
|
1
|
+
import { normalizeStoredAttachments } from './media.js';
|
|
3
2
|
function isRecord(value) {
|
|
4
3
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
4
|
}
|
|
@@ -18,14 +17,17 @@ function normalizeTimestamp(value) {
|
|
|
18
17
|
}
|
|
19
18
|
return null;
|
|
20
19
|
}
|
|
21
|
-
function
|
|
22
|
-
const timestamp = normalizeTimestamp(value)
|
|
23
|
-
|
|
20
|
+
function requireCreatedAt(value) {
|
|
21
|
+
const timestamp = normalizeTimestamp(value);
|
|
22
|
+
if (!timestamp) {
|
|
23
|
+
throw new Error('Message is missing canonical createdAt');
|
|
24
|
+
}
|
|
25
|
+
return timestamp.toISOString();
|
|
24
26
|
}
|
|
25
|
-
function
|
|
26
|
-
if (value === '
|
|
27
|
-
return
|
|
28
|
-
|
|
27
|
+
function requireSenderType(value) {
|
|
28
|
+
if (value === 'human' || value === 'ai_agent')
|
|
29
|
+
return value;
|
|
30
|
+
throw new Error('Message is missing canonical senderType');
|
|
29
31
|
}
|
|
30
32
|
function normalizeAgentClientType(value) {
|
|
31
33
|
if (value === 'claude-code'
|
|
@@ -37,7 +39,7 @@ function normalizeAgentClientType(value) {
|
|
|
37
39
|
}
|
|
38
40
|
return undefined;
|
|
39
41
|
}
|
|
40
|
-
function normalizeContentType(value
|
|
42
|
+
function normalizeContentType(value) {
|
|
41
43
|
if (value === 'text'
|
|
42
44
|
|| value === 'image'
|
|
43
45
|
|| value === 'audio'
|
|
@@ -45,10 +47,7 @@ function normalizeContentType(value, attachments) {
|
|
|
45
47
|
|| value === 'contact_card') {
|
|
46
48
|
return value;
|
|
47
49
|
}
|
|
48
|
-
|
|
49
|
-
if (primary === 'image' || primary === 'audio' || primary === 'file')
|
|
50
|
-
return primary;
|
|
51
|
-
return 'text';
|
|
50
|
+
throw new Error('Message is missing canonical contentType');
|
|
52
51
|
}
|
|
53
52
|
function normalizeForwardedFrom(value) {
|
|
54
53
|
if (!isRecord(value))
|
|
@@ -93,22 +92,25 @@ function normalizeContactCard(value) {
|
|
|
93
92
|
}
|
|
94
93
|
export function serializeStoredMessage(input) {
|
|
95
94
|
const { data } = input;
|
|
96
|
-
const attachments =
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const attachments = data.attachments === undefined
|
|
96
|
+
? []
|
|
97
|
+
: normalizeStoredAttachments(data.attachments);
|
|
98
|
+
if (!attachments) {
|
|
99
|
+
throw new Error('Message has malformed attachments');
|
|
100
|
+
}
|
|
99
101
|
const result = {
|
|
100
102
|
id: input.id,
|
|
101
103
|
senderId: typeof data.senderId === 'string' ? data.senderId : '',
|
|
102
|
-
senderType:
|
|
104
|
+
senderType: requireSenderType(data.senderType),
|
|
103
105
|
isOwner: input.isOwner,
|
|
104
|
-
contentType: normalizeContentType(data.contentType
|
|
106
|
+
contentType: normalizeContentType(data.contentType),
|
|
105
107
|
text: typeof data.text === 'string' ? data.text : null,
|
|
106
108
|
attachments,
|
|
107
109
|
mentions: normalizeStringArray(data.mentions),
|
|
108
110
|
replyTo: typeof data.replyTo === 'string' ? data.replyTo : null,
|
|
109
111
|
replyToPosition: typeof data.replyToPosition === 'number' ? data.replyToPosition : null,
|
|
110
112
|
status: data.status === 'read' ? 'read' : 'sent',
|
|
111
|
-
createdAt:
|
|
113
|
+
createdAt: requireCreatedAt(data.createdAt),
|
|
112
114
|
};
|
|
113
115
|
const forwardedFrom = normalizeForwardedFrom(data.forwardedFrom);
|
|
114
116
|
if (data.forwarded === true || forwardedFrom) {
|
package/dist/turnProtocol.d.ts
CHANGED
|
@@ -3,28 +3,31 @@ export type TurnLifecycleState = 'idle' | 'thinking' | 'streaming' | 'tool' | 'w
|
|
|
3
3
|
export type TurnMessageSemantics = 'progress' | 'turn_complete' | 'control';
|
|
4
4
|
export interface TurnMetadata {
|
|
5
5
|
turnSemantics?: TurnMessageSemantics;
|
|
6
|
-
turnComplete?: boolean;
|
|
7
6
|
replyBehavior?: 'allow_auto_reply' | 'suppress_auto_reply';
|
|
8
7
|
}
|
|
9
8
|
export interface TurnStateLike {
|
|
10
9
|
state: TurnLifecycleState;
|
|
10
|
+
openedAt?: number;
|
|
11
|
+
updatedAt?: number;
|
|
12
|
+
turnUpdatedAt?: number | null;
|
|
11
13
|
}
|
|
14
|
+
export declare const ACTIVE_TURN_STALE_THRESHOLD_MS: number;
|
|
15
|
+
export declare const WAITING_INPUT_STALE_THRESHOLD_MS: number;
|
|
12
16
|
export declare function normalizeTurnMetadata(metadata: unknown): TurnMetadata | null;
|
|
13
17
|
export declare function isTurnOpen(turnState: TurnStateLike | null | undefined): boolean;
|
|
18
|
+
export declare function getTurnStateStaleThresholdMs(state: TurnLifecycleState): number;
|
|
19
|
+
export declare function isTurnStateStale(turnState: TurnStateLike | null | undefined): boolean;
|
|
14
20
|
export declare function resolveTurnMessageSemantics(input: {
|
|
15
21
|
senderType: SenderType;
|
|
16
22
|
metadata?: unknown;
|
|
17
|
-
senderTurnState?: TurnStateLike | null;
|
|
18
23
|
}): TurnMessageSemantics;
|
|
19
24
|
export declare function shouldPromoteConversationMessage(input: {
|
|
20
25
|
senderType: SenderType;
|
|
21
26
|
metadata?: unknown;
|
|
22
|
-
senderTurnState?: TurnStateLike | null;
|
|
23
27
|
}): boolean;
|
|
24
28
|
export declare function shouldTriggerAgentTurn(input: {
|
|
25
29
|
senderType: SenderType;
|
|
26
30
|
metadata?: unknown;
|
|
27
|
-
senderTurnState?: TurnStateLike | null;
|
|
28
31
|
}): {
|
|
29
32
|
allow: boolean;
|
|
30
33
|
semantics: TurnMessageSemantics;
|
package/dist/turnProtocol.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export const ACTIVE_TURN_STALE_THRESHOLD_MS = 30 * 60 * 1000;
|
|
2
|
+
export const WAITING_INPUT_STALE_THRESHOLD_MS = 12 * 60 * 60 * 1000;
|
|
1
3
|
function isRecord(value) {
|
|
2
4
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
5
|
}
|
|
@@ -23,31 +25,52 @@ export function normalizeTurnMetadata(metadata) {
|
|
|
23
25
|
|| metadata.replyBehavior === 'suppress_auto_reply'
|
|
24
26
|
? metadata.replyBehavior
|
|
25
27
|
: undefined;
|
|
26
|
-
if (!turnSemantics &&
|
|
28
|
+
if (!turnSemantics && !replyBehavior) {
|
|
27
29
|
return null;
|
|
28
30
|
}
|
|
29
31
|
return {
|
|
30
32
|
...(turnSemantics ? { turnSemantics } : {}),
|
|
31
|
-
...(typeof metadata.turnComplete === 'boolean' ? { turnComplete: metadata.turnComplete } : {}),
|
|
32
33
|
...(replyBehavior ? { replyBehavior } : {}),
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
export function isTurnOpen(turnState) {
|
|
36
37
|
if (!turnState)
|
|
37
38
|
return false;
|
|
38
|
-
|
|
39
|
+
const open = turnState.state !== 'idle'
|
|
39
40
|
&& turnState.state !== 'completed'
|
|
40
41
|
&& turnState.state !== 'interrupted';
|
|
42
|
+
if (!open)
|
|
43
|
+
return false;
|
|
44
|
+
return !isTurnStateStale(turnState);
|
|
45
|
+
}
|
|
46
|
+
export function getTurnStateStaleThresholdMs(state) {
|
|
47
|
+
return state === 'waiting_input'
|
|
48
|
+
? WAITING_INPUT_STALE_THRESHOLD_MS
|
|
49
|
+
: ACTIVE_TURN_STALE_THRESHOLD_MS;
|
|
50
|
+
}
|
|
51
|
+
export function isTurnStateStale(turnState) {
|
|
52
|
+
if (!turnState)
|
|
53
|
+
return false;
|
|
54
|
+
if (turnState.state !== 'thinking'
|
|
55
|
+
&& turnState.state !== 'streaming'
|
|
56
|
+
&& turnState.state !== 'tool'
|
|
57
|
+
&& turnState.state !== 'waiting_input') {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const updatedAt = turnState.turnUpdatedAt
|
|
61
|
+
?? turnState.updatedAt
|
|
62
|
+
?? turnState.openedAt;
|
|
63
|
+
if (updatedAt == null)
|
|
64
|
+
return false;
|
|
65
|
+
return Date.now() - updatedAt >= getTurnStateStaleThresholdMs(turnState.state);
|
|
41
66
|
}
|
|
42
67
|
export function resolveTurnMessageSemantics(input) {
|
|
43
68
|
const turnMetadata = normalizeTurnMetadata(input.metadata);
|
|
44
69
|
if (turnMetadata?.turnSemantics)
|
|
45
70
|
return turnMetadata.turnSemantics;
|
|
46
|
-
if (turnMetadata?.turnComplete === true)
|
|
47
|
-
return 'turn_complete';
|
|
48
71
|
if (input.senderType === 'human')
|
|
49
72
|
return 'turn_complete';
|
|
50
|
-
return
|
|
73
|
+
return 'progress';
|
|
51
74
|
}
|
|
52
75
|
export function shouldPromoteConversationMessage(input) {
|
|
53
76
|
if (isHiddenRuntimeCardMetadata(input.metadata)) {
|
|
@@ -76,7 +99,12 @@ export function normalizeRuntimeTurnState(value) {
|
|
|
76
99
|
|| value.state === 'waiting_input'
|
|
77
100
|
|| value.state === 'completed'
|
|
78
101
|
|| value.state === 'interrupted') {
|
|
79
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
state: value.state,
|
|
104
|
+
...(typeof value.openedAt === 'number' ? { openedAt: value.openedAt } : {}),
|
|
105
|
+
...(typeof value.updatedAt === 'number' ? { updatedAt: value.updatedAt } : {}),
|
|
106
|
+
...(typeof value.turnUpdatedAt === 'number' ? { turnUpdatedAt: value.turnUpdatedAt } : {}),
|
|
107
|
+
};
|
|
80
108
|
}
|
|
81
109
|
return null;
|
|
82
110
|
}
|